前言
为了巩固提高javascript的水平,我决定手写一些JavaScript中常见的方法,来达到我们熟悉js的目的。
手写深拷贝 ⛹⛹
我们知道,深拷贝其实就是在堆栈中新开辟一个空间,来克隆一个一摸一样的对象。
我们一般实现深拷贝会使用一下方法:
JSON.stringify(JSON.parse(obj))
通过这种方式我们会开辟一个空间,创建一个和obj一摸一样的对象。
那么我们如何手写一个深拷配的方法呢?
// 手写深拷贝 function deepClone(origin, target) { // 首先创建一个变量默认为传入的target对象或者为空对象 var tar = target || {}; var toStr = Object.prototype.toString; var arrayType = "[object Array]"; for (const k in origin) { // 判断origin带有的属性k是继承来的还是创建的 if (origin.hasOwnProperty(k)) { // 判断当前属性是对象和数组之一吗 if (typeof origin[k] == "object" && origin[k] != null) { // 具体判断该属性是数组还是对象 tar[k] = toStr.call(origin[k]) == arrayType ? [] : {}; // 进行递归调用,将里面嵌套的对象或者数组也进行拷贝 deepClone(origin[k], tar[k]); } else { tar[k] = origin[k]; } } } return tar; }
我来给大家讲解一些我的实现思路:
- 首先这个函数传入两个参数,一个要被克隆的原始对象(origin),一个克隆之后的对象(target)
- 然后在这个函数中创建变量tar保存target,或者创建一个空对象
- 接着使用for in 方法对传入的origin对象进行遍历,首先判断origin上的属性k是继承来的还是自己创建的。
- 然后就对传入的对象的属性进行判断,首先使用type of进行简单的一个过滤判断(因为typeof只能粗略的判断一下基本本数据类型,不能更细致的区分),这里还要注意一下因为null类型也会配判断为object类型,所以我们还要排除一下null类型。
- 然后我们就要区分是对象还是数组了,在这里就需要考察对JavaScript的掌握程度了,我们知道因为基本类型中的object类型下面又可以细分为function,array,object,null等类型,要想具体的将他们给区分开就需要使用Object.prototype.toString中的call方法。
Object.prototype.toString.call(null); // "[object Null]" Object.prototype.toString.call(undefined); // "[object Undefined]" Object.prototype.toString.call(“abc”);// "[object String]" Object.prototype.toString.call(123);// "[object Number]" Object.prototype.toString.call(true);// "[object Boolean]"
这个可以清楚的分清楚所属的类型
- 所以我们可以通过这行代码来实现针对属性创建一个对象或者数组
tar[k] = toStr.call(origin[k]) == arrayType ? [] : {};
- 然后通过递归调用传入origin[k],tar[k],因为此时我们已经可以判断出来origin[k]是对象还是数组了,所以我们传入originp[k]就相当于是最初传入origin一样,传入tar[k],就像是传入target一样。
- 然后我们就要给递归一个出口:如果origin中的这个属性不是对象或者数组,那么我们就可以直接进行赋值操作,将origin[k]赋值给tar[k]。
- 最后我们将tar给return出去就可以,这样就实现了深拷贝。
手写forEach🛰🛰
废话不多说,先上源代码:
// 手写ForEach方法 Array.prototype.myForEach = function (cb) { // 这里的this指的是调用这个方法的数组 var _arr = this; var len = _arr.length; // 这个obj2代表的是myForEach的第二个参数,这个参数可以改变this指向 var arr2 = arguments[1] || window; // 根据数组的长度来决定cb这个函数执行的次数 for (var i = 0; i < len; i++) { // apply可以更改this的指向 cb.apply(arr2, [_arr[i], i, _arr]); } };
思路:
首先我们要给Array.prototype绑定一个myForeEach方法,这个函数接收一个参数是一个回调函数, 然后我们在函数内部声明一个变量用来保存this,首先我们要在这里明白的是这里的this指向调用他的数组,然后这个arr2保存的是可能会传递的二个参数,这个参数可以更改this的指向。然后我们就可以实现遍历,其实本质就是一个for循环,然后根据数组的长度来调用回调函数。其中apply方法可以更改this的指向,然后第二个参数传递一个数组,里面的参数就是要传入回调函数的参数。
实例数据:
let arr = [ { name: "张三", age: 21, }, { name: "李四", age: 24, }, { name: "王五", age: 31, }, ];
实际使用
arr.myForEach(function (item, index, arr) { // console.log(this); console.log(item); });
手写map方法 🐤🐤
先上源代码:
// 手写map方法 Array.prototype.myMap = function (cb) { var _arr = this; var len = _arr.length; var arr2 = arguments[1] || window; var newArr = []; for (var i = 0; i < len; i++) { // 因为map要返回一个新的数组,所以里面如果有对象这种引用类型,需要进行深拷贝 var _item = deepClone(_arr[i]); // cb调用apply方法需要在cb中进行return,这也是map方法所需要做的 newArr.push(cb.apply(arr2, [_item, i, _arr])); } return newArr; };
注意这个方法前三行代码和forEach是相同的不再赘述,我们主要说不同的地方,首先我们还是使用for循环但是注意这里我们使用了前面写好的深拷贝函数deepClone克隆数组中的元素,然后创建一个新的空数组,将回调函数中return的元素push到newArr中,最后再将newArr给return出去,这样就完成了myMap方法
实际使用:
let newArr = arr.myMap(function (item, index, arr) { item.age += 100; return item; }); console.log(newArr);
这里的arr和示例数据相同,我们可以查看一下返回值:
手写filter方法 🥇🥇
我们知道过滤在我们实习的很多项目中都会使用到,可以用来筛选有用的数据,所以我们这次就手写一个
废话不多说,先上代码:
// 手写filter方法 Array.prototype.myFilter = function (cb) { var _arr = this; var len = _arr.length; var arr2 = arguments[1] || window; var newArr = []; var _item; for (var i = 0; i < len; i++) { _item = deepClone(_arr[i]); cb.apply(arr2, [_item, i, _arr]) ? newArr.push(_item) : ""; } return newArr; };
思路:
这个代码实现上面其实和之前map的实现方式有一点像,但略有不同,首先相同的是我们要对数组中的元素进行深拷贝,因为涉及对象这个引用类型,为了防止修改原对象就进行深拷贝,我们知道filter方法返回值其实是符合条件的值,所以可以进行判断如果这个值满足要求,那就将他push到newArr中。
实际使用:
let newArr2 = arr.myFilter(function (item, index, arr) { return item.age > 25; }); console.log(newArr2);
总结 🎃🎃
以上就是暂时手写出来的数组方法,后续还会继续更新手写数组方法的内容,通过手写方法可以是我们更加熟悉JavaScript的语法,提升js的基础