前端高频手撕代码整理(一)
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 }

浙公网安备 33010602011771号