Fork me on GitHub

前端高频手撕代码整理(一)

1. 手写闭包

首先,看一个简单的案例:

1 for (var i = 0; i < 4; i++) {
2     setTimeout(() => {
3         console.log(i);
4     }, i * 1000);
5 }
6 //  每隔一秒打印 4 4 4 4
因为var 没有块级作用域,循环变量变成全局变量。如何解决?----> 闭包
1 for (var i = 0; i < 4; i++) {
2     (function(j) {
3         setTimeout(() => {
4             console.log(j);
5         }, j * 1000);
6     })(i)
7 }
8 // 每隔一秒打印 0 1 2 3 

补:还可以通过let解决。

2. 手写实现call、apply、bind

(1)call

test.call(a)

call首先将test的this指向a,然后执行函数test。

我们可以将函数test挂载到a上面,让函数test变成a的一个方法,通过a.test来调用

 1 Function.prototype.myCall = function(context) {
 2     context = context || window; //没传入this 默认绑定window对象
 3     context.fn = this; // 将myCall函数的调用者test挂载到context的属性上,可以通过context.fn调用test函数.这里的context就是a
 4     const args = [...arguments].slice(1); // 将第一个参数去掉,即去掉test.myCall中的a,然后args=[1,2]
 5     const results = context.fn(...args); // 去调用test(1,2)
 6     delete context.fn
 7     return results
 8 }
 9 
10 function test(arg1, arg2) {
11     console.log(arg1, arg2);
12     console.log(this.name, this.offer);
13 }
14 const a = {
15     name: 'coder',
16     offer: 100
17 }
18 test.myCall(a, 1, 2)

1 2
coder 100

 (2)apply

apply和call 的区别就是:apply的第二个参数必须是数组

 1 Function.prototype.myApply = function(context) {
 2     var context = context || window
 3     context.fn = this
 4     var result
 5         // 需要判断是否存储第二个参数
 6         // 如果存在,就将第二个参数展开
 7     if (arguments[1]) {
 8         if (Array.isArray(arguments[1])) {
 9             result = context.fn(...arguments[1])
10         } else {
11             console.log("输入的参数不是数组");
12         }
13 
14     } else {
15         result = context.fn()
16     }
17 
18     delete context.fn
19     return result
20 }

(3)bind

bind和另两个的区别就是:bind返回一个新的函数,但不调用

 1 Function.prototype.myBind = function(context) {
 2     if (typeof this != 'function') {
 3         throw new TypeError('Error')
 4     }
 5     var _this = this
 6     var args = [...arguments].slice(1)
 7     return function F() {
 8         if (this instanceof F) {
 9             return new _this(...args, ...arguments)
10         }
11         return _this.apply(context, args.concat(...arguments))
12     }
13 }

 3. new运算符的实现

new运算符做了这些事情:

  • 创建一个空对象;
  • 构造函数中的this指向该空对象;
  • 执行构造函数为这个空对象添加属性;
  • 确保返回值是一个对象
 1 function create() {
 2     // 创建一个空的对象
 3     let obj = new Object()
 4     // 获得构造函数
 5     let Con = [].shift.call(arguments)
 6     // 链接到原型
 7     obj.__proto__ = Con.prototype
 8     // 绑定 this,执行构造函数
 9     let result = Con.apply(obj, arguments)
10     // 确保 new 出来的是个对象
11     return typeof result === 'object' ? result : obj
12 }

其中:

1 [].shift.call(arguments) // 表示拿到arguments的第一项。比如arguments=[1, 2, 3],则返回 1

4. Promise.all的实现

 1 Promise.all = function(iterator) {
 2   if (!Array.isArray(iterator)) return;
 3   let count = 0;
 4   let res = [];
 5   return new Promise((resolve, reject) => {
 6     for (let item of iterator) {
 7       Promise.resolve(item)
 8         .then(data => {
 9           res[count++] = data;
10           if (count == iterator.length) {
11             resolve(res)
12           }
13         })
14         .catch(e => {
15           reject(e)
16         })
17     }
18   })
19 }

 5. 防抖和节流

防抖节流的作用都是防止函数多次调用,区别在于:

  • 防抖:在一定时间内只执行一次(多次执行变一次)
  • 节流:经过一定时间间隔就执行一次(多次执行变成每隔一段时间执行一次)

防抖:

 1 // func是用户传入需要防抖的函数
 2 // wait是等待时间
 3 const debounce = (func, wait = 50) => {
 4   // 缓存一个定时器id
 5   let timer = 0
 6   // 这里返回的函数是每次用户实际调用的防抖函数
 7   // 如果已经设定过定时器了就清空上一次的定时器
 8   // 开始一个新的定时器,延迟执行用户传入的方法
 9   return function(...args) {
10     if (timer) clearTimeout(timer)
11     timer = setTimeout(() => {
12       func.apply(this, args)
13     }, wait)
14   }
15 }
16 // 不难看出如果用户调用该函数的间隔小于wait的情况下,上一次的时间还未到就被清除了,并不会执行函数

节流:

 1 /**
 2  * underscore 节流函数,返回函数连续调用时,func 执行频率限定为 次 / wait
 3  *
 4  * @param  {function}   func      回调函数
 5  * @param  {number}     wait      表示时间窗口的间隔
 6  * @param  {object}     options   如果想忽略开始函数的的调用,传入{leading: false}。
 7  *                                如果想忽略结尾函数的调用,传入{trailing: false}
 8  *                                两者不能共存,否则函数不能执行
 9  * @return {function}             返回客户调用函数
10  */
11 _.throttle = function(func, wait, options) {
12     var context, args, result;
13     var timeout = null;
14     // 之前的时间戳
15     var previous = 0;
16     // 如果 options 没传则设为空对象
17     if (!options) options = {};
18     // 定时器回调函数
19     var later = function() {
20       // 如果设置了 leading,就将 previous 设为 0
21       // 用于下面函数的第一个 if 判断
22       previous = options.leading === false ? 0 : _.now();
23       // 置空一是为了防止内存泄漏,二是为了下面的定时器判断
24       timeout = null;
25       result = func.apply(context, args);
26       if (!timeout) context = args = null;
27     };
28     return function() {
29       // 获得当前时间戳
30       var now = _.now();
31       // 首次进入前者肯定为 true
32       // 如果需要第一次不执行函数
33       // 就将上次时间戳设为当前的
34       // 这样在接下来计算 remaining 的值时会大于0
35       if (!previous && options.leading === false) previous = now;
36       // 计算剩余时间
37       var remaining = wait - (now - previous);
38       context = this;
39       args = arguments;
40       // 如果当前调用已经大于上次调用时间 + wait
41       // 或者用户手动调了时间
42        // 如果设置了 trailing,只会进入这个条件
43       // 如果没有设置 leading,那么第一次会进入这个条件
44       // 还有一点,你可能会觉得开启了定时器那么应该不会进入这个 if 条件了
45       // 其实还是会进入的,因为定时器的延时
46       // 并不是准确的时间,很可能你设置了2秒
47       // 但是他需要2.2秒才触发,这时候就会进入这个条件
48       if (remaining <= 0 || remaining > wait) {
49         // 如果存在定时器就清理掉否则会调用二次回调
50         if (timeout) {
51           clearTimeout(timeout);
52           timeout = null;
53         }
54         previous = now;
55         result = func.apply(context, args);
56         if (!timeout) context = args = null;
57       } else if (!timeout && options.trailing !== false) {
58         // 判断是否设置了定时器和 trailing
59         // 没有的话就开启一个定时器
60         // 并且不能不能同时设置 leading 和 trailing
61         timeout = setTimeout(later, remaining);
62       }
63       return result;
64     };
65   };

 6.深浅拷贝

浅拷贝:(两种方法)

1 let newObj = Object.assign({}, obj);
1 let newObj = {...obj}

深拷贝:(两种方法)

1 let newObj = JSON.parse(JSON.stringify(oldObj))

  该方法有局限性:

  • 会忽略 undefined
  • 会忽略 symbol
  • 不能序列化函数
  • 不能解决循环引用的对象
  • 使用递归↓
 1 function deepClone(obj) {
 2   if (!obj || typeof obj !== 'object') return
 3   let newObj = Array.isArray(obj) ? [] : {}
 4   for (let key in obj) {
 5     if (obj.hasOwnProperty(key)) {
 6       newObj[key] =
 7         typeof obj[key] === 'object' ? deepClone(obj[key]) : obj[key]
 8     }
 9   }
10   return newObj
11 }

 

posted @ 2021-04-29 21:11  zerozhupan  阅读(425)  评论(0)    收藏  举报