JS中的编程题

本文分享了一系列实用的JavaScript编程技巧,包括函数实现如去除7及其倍数的自然数输出、斐波那契数列计算、函数柯里化、防抖与节流等高级功能的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.实现一个函数printNum,接受一个参数n,输出n个递增自然数 ·

输出的自然数不能含有7(17,71,176)或为7的倍数(14,63);如果含有7或为7的倍数,则输出下一个自然数;

· 支持多次调用,从0开始,每次自上次调用的末尾自然数继续打印

· 例子:

printNum(); // 0
printNum(2); // 1,2
printNum(5); // 3,4,5,6,8
printNum(17); // 9,10,11,...,26,29,30
复制代码

思路:

1.对参数为undefined特殊处理
2.输入的参数为输出数字的长度
3.对数字进行判断不能含有7或7的倍数
4.需要对每次最后输出的值进行记忆
5.将数组转为字符串输出
复制代码

考察知识点:

1.数组方法的使用
2.正则基本功
复制代码

代码:

let lastNum = 0
function printNum (num) {
    if (!num) {
        return 0
    }
    
    let arr = []
    for (let i = 0; i < num; i++ ) {
        lastNum ++
        if (lastNum % 7 === 0 || /[7]/.test(lastNum) ) {
            num ++
            // 规避了 27 28这种情况
        }
        if (lastNum % 7 !== 0 && /[7]/.test(lastNum) ) {
            arr.push(lastNum)
        }
        
    }
    return arr.toString()
}

复制代码
最终输出结果与题目一致,但此解法并不完美,1.判断了两次7的倍数和含有7的情况 2.动态改变了for循环的num值
复制代码

优化

let i = -1
function printNum (n) {
    let res = []
    res.push(++i)
    
    while(n > 1) {
        i++
        if (i % 7 != 0 && !(/[7]/.test(i))) {
            res.push(i)
            n--
        }
    }
    return res.join(',')
}
复制代码

用闭包实现

function printNum (n) {
    let i = -1
    return function temp (n) {
        let res = []
        ++i
        if (i % 7 === 0 || /[7]/.test(i)) {
            printNum(n)
        }
        res.push(i)
        if (n < 0) {
            return res.push(i)
        }
        while (n > 1) {
            i++
            if (i % 7 !== 0 && !(/[7]/.test(i)) {
                res.push(i)
                n--
            }
        }
        return res.join(',')
    }
}

复制代码

2.实现一个函数,接受一个url数组作为参数,返回一个Promise。

  • 要求同时发送请求获取资源内容(发送请求使用Fetch,fetch函数说明:fetch()必须接受一个参数-----资源的路径。无论请求成功与否,它都返回一个promise对象,resolve对应请求的Response)
  • 当任意一个资源加载成功(通过请求的status状态即可)则将promise的状态置为resolve,并将该url作为resolve的参数
  • 当所有的资源都不成功时,promise状态置为reject
题目解析(感觉这道题目不是重点)
1.参数为url数组
2.同时发送请求(让我想到了预加载图片、上传多个文件)
复制代码
  1. 写出如下输出值
this.a = 20
var test = {
    a: 40,
    init: () => {
        console.log(this.a)
        function go () {
            console.log(this.a)
        }
        go.prototype.a = 50
        return go
    }
}
var p = test.init() // 20 先执行 . 再执行 ()
p() // 20
复制代码

箭头里的this指向顶级对象test的作用域,指向window

this.a = 20
var test = {
    a: 40,
    init: function () {
        console.log(this.a)
        function go () {
            console.log(this.a)
        }
        go.prototype.a = 50
        return go
    }
}
test.init() // 40
复制代码
var handler = {
  id: '123456',

  init: function() {
    document.addEventListener('click',
      event => console.log(this), false);
  }
}
handler.init() // 调用打印的this是hander对象

var handler = {
  id: '123456',

  init: ()=> {
    console.log(this)
  }
}
handler.init() // 调用打印的this是window
复制代码

3.分别使用递归和迭代的方式实现斐波那契数列值的计算: F(0) = 0,F(1) = 1,F(n) = F(n - 1) + F(n - 2);(n>=2, n为正整数)

// 递归
function fib (n) {
    if (n == 1 || n == 2) {
        return 1
    }
    return fib(n-1) + fib(n-2)
}
fib(10) // 55
复制代码

时间复杂度为O(2^n),空间复杂度为O(n)

// 非递归
function fib (n) {
    var res = [1, 1]
    if (n === 1 || n ===2) {
        return 1
    } 
    for (var i = 2; i < n; i++) {
        res[i] = res[i-1] + res[i-2]
    }
    return res[n-1]
}
fib(10) // 55
复制代码

时间复杂度为O(n),空间复杂度为O(n)

for循环版本(递归有性能问题,用循环来做。)

function fibonacci (n) {
    var last = 1
    var last2 = 0
    var current = last2
    for (var i = 1; i <= n; i++) {
        last2 = last
        last = current
        current = last + last2
    }
    return current
}
复制代码
// 非递归
function fib (n) {
    var a, b ,res;
    a = b = 1;
    for (var i = 3;i <= n; i++) {
        res = a + b
        a = b
        b = res
    }
    return res
}
复制代码

时间复杂度为O(n),空间复杂度为O(1)

function fibon(a,b,n) {
    if (n<=2){
        return a;
    }
    return fibon(a+b,a,n-1);
}
console.log(fibon(1,1,10));//求数列中第十个数
复制代码

4.bind 看官方的polyfill

  1. bind
  2. arguments
  3. 原型
if (!Function.prototype.bind) {
    Function.prototype.bind = function (onThis) {
        if(typeof this != "function") {
            throw new Error("请使用函数调用bind")
        }
        var args = Arrays.prototype.slice.call(arguments, 1),
        fToBind = this,
        fNOP = function () {},
        fBound = function () {
            console.log('args', args)
            const _args = args.concat(Array.prototype.slice.call(arguments))
            // bind 核心, this instanctof fBound == true的时候,说明返回的fBound被当做new的构造函数调用
            // 模拟原生bind的多个参数
            return fToBind.apply( this instanctof fBound ? this : onThis, _args)
        }
        // 维护原型链
        if (this.prototype) {
            fNOP.prototype = this.prototype
        }
        // 同一个堆地址 fBound.prototype = this.prototype 是错误的
        // 修正函数名和构造函数
        fBound.prototype = new fNOP()
      return fBound
    }
}
复制代码
this.age = 18
function people (data,data1) {
    this.cons = "构造函数"
    console.log(this.age)
    console.log(data)
    console.log(data1)
}
test.prototype.init = function () {
    console.log()
}
var result = test.bind(this, "Fruit")
var result1 = new result("Fruit Bro")
复制代码

5.异步错误能被捕获到吗

第一种

try {
    console.log(1)
    setTimeout(() => {
        console.log(2)
    }, 100);
    setTimeout(() => {
        console.log(3)
        throw new Error(5)
    }, 0);
    console.log(4)
} catch (error) {
    console.log(`错啦 ${error}`)
}
复制代码

第二种

try {
    console.log(1)
    setTimeout(() => {
        console.log(2)
    }, 100);
    console.log(3)
    throw new Error(5)
    console.log(4)
} catch (error) {
    console.log(`错啦 ${error}`)
}
复制代码

自己先猜想一下吧,然后再控制台验证一下自己的猜想

执行结果如下

  • 第一种
    分析:运行中直接抛出了错误,而没有进入catch,说明异常中的错误未被正常捕获到,那么,问题来了,对于异步的错误改如何处理呢?欢迎说出你的想法
  • 第二种
    可以发现,正常打印了 【错啦 Error: 5】说明错误被catch捕获到了,对于同的错误的捕获没有任何问题

6.实现函数柯里化

function composeFunctions () { var args = Array.prototype.slice.apply(arguments)

var _func = fucntion () {
    if (arguments.length === 0) {
        return func.apply(this, args)
    }
    
    Array.prototype.push.apply(args, arguments)
    
    return _func
}

return _func
复制代码

}

7.简短优雅地实现sleep函数

async function test() {
    console.log('hello')
    await sleep(1000)
    console.log('world')
}

function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms))
}

test()
复制代码

8.防抖

防抖和节流的作用都是防止函数都次调用。区别在于,假设一个用户一直触发这个函数,且每次触发函数的间隔小于wait,防抖的情况下只会调用一次,而节流的情况会每隔一定时间(参数wait)调用函数。

// func是用户传入需要防抖的函数
// wait是等待时间
const debounce = (func, wait = 50) => {
    // 缓存一个定时器id
    let timer = 0
    // 这里返回的函数是每次用户实际调用的防抖函数
    // 如果已经设定过定时器了就清空上一次的定时器
    // 开始一个新的定时器,延迟执行用户传入的方法
    return function (...args) {
        if (timer) clearTimeout(timer)
        timer = setTimeout(() => {
            func.apply(this, args)
        }, wait)
    }
}
复制代码

使用

function handleClick (...args) {
    //...
}

let a = debounce(handleClick, 1000)
// 每次执行时调用a就可以了
复制代码

来实现一个带有立即执行选项的防抖函数

例如用户点star的时候,我们希望用户点第一下的时候就去调用接口,并且成功之后改变star按钮的样子,用户就可以立马得到反馈是否star成功了,这个情况适用立即执行的防抖函数,它总是在第一次调用,并且下一次调用必须与前一次调用的时间间隔大于wait才会触发。

// 获取当前时间戳
function now () {
    return +new Date()
}
/**
 * 防抖函数,返回函数连续调用时,空闲时间必须大于或等于 wait,func 才会执行
 *
 * @param  {function} func        回调函数
 * @param  {number}   wait        表示时间窗口的间隔
 * @param  {boolean}  immediate   设置为ture时,是否立即调用函数
 * @return {function}             返回客户调用函数
 */
 
 function debounce (func, wait = 50, immediate = true) {
     let timer, context, args
     // 延迟执行函数
     const later = () => setTimeout(() => {
         // 延迟函数执行完毕,清空缓存的定时器序号
         timer = null
         // 延迟执行的情况下,函数会在延迟函数中执行
         // 使用到之前缓存的参数和上下文
         if (!immediate) {
             func.apply(context, args)
             context = args = null
         }
     }, wait)
     
     // 这里返回的函数是每次实际调用的函数
     return function(...params) {
     // 如果没有创建延迟执行函数(later),就创建一个
         if (!timer) {
             timer = later()
             // 如果是立即执行,调用函数
             // 否则缓存参数和调用上下文
             if (immediate) {
                 func.apply(this, params)
             } else {
                 context = this
                 args = params
             }
             // 如果已有延迟执行函数(later),调用的时候清除原来的并重新设定一个
             // 这样做延迟函数会重新计时
         } else {
             clearTimeout(timer)
             timer = later()
         }
     }
 }
 
复制代码

总结:

  • 对于按钮防点击来说的实现:如果函数是立即执行的,就立即调用,如果函数式延迟执行的,就缓存上下文和参数,放到延迟函数中去执行。一旦我开始一个定时器,只要我定时器还在,你每次点击我都重新计时。一旦你点累了,定时器时间到,定时器重置为null,就可以再次点击了。
  • 对于延时执行函数来说的实现:清除定时器ID,如果是延迟调用就调用函数。

9.节流

防抖和节流本质是不一样的。防抖动是将多次执行变为最后一次执行,节流是将多次执行变成每隔一段时间执行。

/**
 * underscore 节流函数,返回函数连续调用时,func 执行频率限定为 次 / wait
 *
 * @param  {function}   func      回调函数
 * @param  {number}     wait      表示时间窗口的间隔
 * @param  {object}     options   如果想忽略开始函数的的调用,传入{leading: false}。
 *                                如果想忽略结尾函数的调用,传入{trailing: false}
 *                                两者不能共存,否则函数不能执行
 * @return {function}             返回客户调用函数
 */
 
 _.throttle = function(func, wait, options) {
     var context, args, result
     var timeout = null
     // 之前的时间戳
     var previous = 0
     // 如果 options 没传则设为空对象
     if (!option) options = {}
     // 定时器回调函数
     var later = function () {
         // 如果设置了 leading,就将 previos 设为 0
         // 用于下面函数的第一个 if 判断
         previous = options.leading === false ? 0 : _.now()
         // 置空一是为了防止内存泄漏,二是位了下面的定时器判断
         timeout = null
         result = func.apply(context, args)
         if (!timeout) context = args = null
     }
     return function () {
         // 获得当前时间戳
         var now = _.now()
         // 首次进入前者肯定为 true
	     // 如果需要第一次不执行函数
	     // 就将上次时间戳设为当前的
         // 这样在接下来计算 remaining 的值时会大于0
         if (!previous && options.leading === false) previous = now
         // 计算剩余时间
         var remaining = wait - (now - previous)
         context = this
         args = arguments
          // 如果当前调用已经大于上次调用时间 + wait
          // 或者用户手动调了时间
     	  // 如果设置了 trailing,只会进入这个条件
    	  // 如果没有设置 leading,那么第一次会进入这个条件
    	  // 还有一点,你可能会觉得开启了定时器那么应该不会进入这个 if 条件了
    	  // 其实还是会进入的,因为定时器的延时
    	  // 并不是准确的时间,很可能你设置了2秒
    	  // 但是他需要2.2秒才触发,这时候就会进入这个条件
    	  if (remaining <= 0 || remaining > wait) {
    	      // 如果存在定时器就清理掉否则会调用二次回调
    	      if (timeout) {
    	          clearTimeout(timeout)
    	          timeout = null
    	      }
    	      previous = now
    	      result = func.apply(context, args)
    	      if (!timeout) context = args = null
    	  } esle if (!timeout && options.trailing !== false) {
    	      // 判断是否设置了定时器和 trailing
        	  // 没有的话就开启一个定时器
              // 并且不能不能同时设置 leading 和 trailing
              timeout = setTimeout(later, remaining)
    	  }
    	  return result
     }
 }
 
复制代码

10.变量提升

test()
alert(a) // undefined
var flag = true
if (!flag) {
    var a = 1
}
if (flag) {
    function test () {
        console.log('test1')
    }
} else {
    function test () {
        console.log('test2')
    }
}
复制代码
function test () {
    console.log('1')
}
function init () {
    test()
    if (false) {
        function test () {
            console.log('2')
        }
    }
}
init()
复制代码
function func(flag) {
    if (flag) {
        function getValue() {
            return 'a'
        }
    } else {
        function getValue () {
            return 'b'
        }
    }
    return getValue()
}
console.log(func(true))
// 上面这段代码相当于
function functions(flag) {
    function getValue(){return 'a'};
    function getValue(){return 'b'};
    if (flag) {
      ....
    } else {
      ....
    }
 
    return getValue();
}
// 如何解决
function functions(flag) {
    if (flag) {
      var getValue = function () { return 'a'; }
    } else {
      var getValue = function () { return 'b'; }
    }
    return getValue();
}
// 用函数表达式的方法,因为函数表达式不会提升,因此只有当逻辑执行到这儿的时候才会执行。避免了函数声明提升的问题。
复制代码
console.log(a) // f a() {}
var a = 1
function a () {
    
}
console.log(a) // 1
复制代码

11.在一个数组中找出和为m的两个数,拓展一下,一个数组中找出n个数,使其和为m

假设:找出和为10的两个数

const arr = [5, 6, 2, 1, 7, 9, 4, 8]

解法一: 穷举

1.将数组排序
2.while循环

const arr = [5, 6, 2, 1, 7, 9, 4, 8]

function sortArr (arr) {
  return arr.sort((a, b) => {
    return a - b
  })
}

function getSumNum (arr, Sum) {
  let newArr = sortArr(arr)
  let i = 0
  let j = arr.length -1
  while (i < j) {
    if (i === j) return
    if (newArr[i] + newArr[j] === Sum) {
      console.log( `${newArr[i]}${newArr[j]}` ) 
      i++
      j--
    } else if (newArr[i] + newArr[j] < Sum) {
      i++
    } else {
      j--
    }
  }
  return 'none'
}

// 1 和 9
// 2 和 8
// 4 和 6
// none
复制代码

转载于:https://juejin.im/post/5bf35e976fb9a049d61d2494

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值