说明
本文主要撰写比较适用、耐用,及常见处理业务的js方法,同时会对部分内置的js方法进行手写,旨在一方面掌握这些提高效率的js方法,另一方面对内置方法的手写剖析,加深大家对方法的封装逻辑。
本文属于长期贴,会在发布的前提下进行不定期更新,为了避免错乱,该文章的目录结构会根据更新点划分。本人会在实际开发中尽量多的学习和掌握新的知识点,并不断完善该文章的内容。
读者如有发现本文在编写过程中有不对的、不恰当的地方,望及时告知,不吝感谢!
一、更新一(2022.05.17)
1.实现数组和对象的forEach
我们平时开发中经常在使用forEach
,那么有没有思考过forEach
内部是怎么实现的呢?了解真相才能实现真正的自由
,现在我们就要剖析一下forEach
内部的逻辑。
须知:
内置的forEach
只能遍历数组,遍历对象时报错;
手写的方法支持遍历数组和对象;
手写的forEach
只是单纯的实现功能方法,并没有挂载原型上;
本方法手写内容思路源于axios
源码中,可至utils的工具函数
查看。
const _toString = Object.prototype.toString;
//封装判断是否是数组的方法 function isArray(value) { return _toString.call(value) === "[object Array]"; }
//封装forEach function forEach(val, fn) { // (1)null和undefined时,直接返回,不做处理 if (val === null || val === undefined) { return; } // (2)如果不是对象,则转换成数组类型 if (typeof val !== "object") { val = [val]; } // (3)分别处理数组和对象的情况 if (isArray(val)) { for (let i = 0, j = val.length; i < j; i++) { //回调函数内this指向改为null fn.call(null, val[i], i, val); } } else { for (const k in val) { // for in 遍历对象是包括了原型链上的可枚举属性,使用hasOwnProperty只选择实例对象上的可枚举属性 if (val.hasOwnProperty(k)) { //回调函数内this指向改为null fn.call(null, val[k], k, val); } } } }
// 例子: const arr1 = [1, 2, 3, 4, 5]; const arr2 = { id: 1, name: "阿离", age: 18 };
forEach(arr1, (item, index, arr) => { console.log(item, index, arr); }); // 1 0 [ 1, 2, 3, 4, 5 ] // 2 1 [ 1, 2, 3, 4, 5 ] // 3 2 [ 1, 2, 3, 4, 5 ] // 4 3 [ 1, 2, 3, 4, 5 ] // 5 4 [ 1, 2, 3, 4, 5 ]
forEach(arr2, (item, index, arr) => { console.log(item, index, arr); }); // 1 id { id: 1, name: '阿离', age: 18 } // 阿离 name { id: 1, name: '阿离', age: 18 } // 18 age { id: 1, name: '阿离', age: 18 }
#### 2.手写call()方法我们可以使用`call`方法执行某函数,并**显示调用**改变该方法内部`this`的指向。该方法的语法和作用与 `apply()` 方法类似,只有一个区别,就是 `call()` 方法接受的是**一个参数列表**,而 `apply()` 方法接受的是**一个包含多个参数的数组**。`call()`方法详情可至[MDN](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/call)查阅。```js// 手写callFunction.prototype.alCall = function (thisArg, ...argArray) { // 1.获取函数 var fn = this; // 2.处理this thisArg = thisArg !== null && thisArg !== undefined ? Object(thisArg) : window; // 3.处理函数 thisArg.fn = fn; // 4.执行函数 var result = thisArg.fn(...argArray); delete thisArg.fn; return result;};function sum(num1, num2) { return num1 + num2;}var result = sum.alCall({ name: "aaa" }, 20, 40); // 60var result = sum.alCall("西西", 1, 40); // 41
3.手写apply()方法
上面有提到apply()
和call()
都能改变函数this
指向,并且说明了两者的区别,此处不再赘述。apply()
方法详情可至MDN查阅。
// 1.手写apply//注意apply参数就是以数组或者类数组存在,因此不能用扩展运算符展开argArrayFunction.prototype.alApply = function (thisArg, argArray) { // 2.获取执行函数 var fn = this; // 3.处理传递的指定thisArg thisArg = thisArg !== null && thisArg !== undefined ? Object(thisArg) : window; // 4.把执行的函数的this隐式指向指定的参数 thisArg.fn = fn; // 5.处理参数 argArray = argArray ? argArray : []; // 6.执行函数(实际执行的函数接收的参数需要使用扩展运算符展开) var result = thisArg.fn(...argArray); // 7.移除this上的fn属性 delete thisArg.fn; // 8.返回值 return result;};// 执行函数function fn() { console.log(this);}function sum(num1, num2) { console.log(this); return num1 + num2;}fn.alApply({ name: "ali" }); // this => { name: 'ali'}var result = sum.alApply("aaa", [20, 30]); // this => [String: 'aaa']console.log(result); // 50
4.手写bind()方法
bind()
方法同样可以改变函数的this
指向,但是并不会像call()
和apply()
一样会直接执行函数。bind()
方法创建一个新的函数,在 bind()
被调用时,这个新函数的 this
被指定为 bind()
的第一个参数,而其余参数将作为新函数的参数,供调用时使用。bind()
方法详情可至MDN查阅。
//手写bindFunction.prototype.alBind = function (thisArg, ...argArray) { // 1.获取函数 var fn = this; // 2.获取this thisArg = thisArg !== null && thisArg !== undefined ? Object(thisArg) : window; // 3.返回函数(bind方法返回的是新函数) return function (...arg) { // 4.处理函数this thisArg.fn = fn; // 5.获取最终参数(包括执行bind时接收的其他参数,即执行新函数接收的参数) var finalArgs = [...argArray, ...arg]; var result = thisArg.fn(...finalArgs); delete thisArg.fn; return result; };};function sum(num1, num2, num3, num4) { console.log(this); return num1 + num2;}var foo = sum.alBind({ name: "xixi" }); var result1 = foo(10, 20, 30, 40); // this => { name: "xixi" }, result => 30var result2 = foo( 30, 40); // this => { name: "xixi" }, result => 70原文:https://juejin.cn/post/7098530669122289701