你以为arr.filter遍历的每一项,真的是arr的每一项吗?

xiaoxiao2021-03-01  51

想必大家一定用惯了es6中的各类语法,尤其是数组提供的方法,更是让我们对数组的处理获得了极大的便利。

今天我要讲的其实是一些数组中非常简单的方法,但当你真的研究它的时候,你会发现一些奇妙之处。


一、初温数组的filter方法

let arr = [1,2,3,4,5,6]; let newArr = arr.filter(function(item,index){ return item > 3; }) console.log(newArr);//[4,5,6]

很简单,当return为true的时候,会返回该项,所有返回的项会组成一个新的数组


那么,这个filter到底是怎么实现的呢?我们还是按照老习惯,把它用es5实现出来(如果你看过我其他的文章,应该习惯用es5来介绍es6,这有助于理解es6的语法)

Array.prototype.filter = function(filterFn){ let newArr = []; for(var i = 0;i<this.length;i++){ filterFn(this[i],i) && newArr.push(this[i]); } return newArr; } let arr = [1,2,3,4,5,6]; let newArr = arr.filter(function(item,index){ return item > 3; })

可以拷贝下这段代码去执行下,你会发现跟原生filter结果一模一样。 原理也很简单,遍历该数组,将该数组的每一项传入callback,当满足callback返回值为true的时候,将该项放入新数组中,然后返回这个新数组即可。


其实,这只是一个阉割版的filter,让我们看下MDN是如何实现这个filter的

二、完整版filter

可见,filter是有两个参数的,第一个参数是callback,第二个参数是thisArg

callback:遍历数组时,处理每一项的处理函数 参数: element:正在处理中的数组的当前项,即上例中的itemindex:当前正在处理中的数组的当前项的索引,即上例中的itemarray:正在处理的数组thisArg:改变正在执行的callback中的this指向为thisArg返回值:当callback返回为true的时候,则返回该项,所有返回的项组成的数组则为返回值,如果没有项返回,返回值则为空数组 所以,其实,我们在写es5去解释es6(暂且称为es6的原理)的时候,至少应该满足MDN所说的那几个要素。 回过头看下之前写的原理,其实只写了callback函数中element和index两个参数,缺少了array,同样的也缺少了thisArg参数。 那么,既然存在,我们就需要将缺少的几个参数功能补全,具体如下: Array.prototype.filtersss = function(filterFn,thisArg){ let newArr = []; for(var i = 0;i<this.length;i++){ filterFn.call(thisArg,this[i],i,this) && newArr.push(this[i]); } return newArr; } let arr = [1,2,3,4,5,6]; let newArr = arr.filtersss(function(item,index,origin){ return item > 3; }) console.log(newArr);//[4,5,6]

同样也非常简单,根据“少什么补什么”的原则,很快就能将原理补全。按照上述原理,将正在处理的数组传入callback中(上例形参origin),将thisArg通过call的方式改变call中的this指向。 这样一看,简直是太简单了!!!


三、意料之外

1.改变this指向

既然原理中满足了MDN的几个要素,那么我们来改变了this指向:

Array.prototype.filtersss = function(filterFn,thisArg){ let newArr = []; for(var i = 0;i<this.length;i++){ filterFn.call(thisArg,this[i],i,this) && newArr.push(this[i]); } return newArr; } let arr = [1,2,3,4,5,6]; let newArr = arr.filtersss(function(item,index,origin){ console.log(typeof this,this);//object,String {"zhuangzhuang"} return item > 3; },'zhuangzhuang') console.log(newArr);//[4,5,6]

那么我们原生的filter是怎么样的呢?

let arr = [1,2,3,4,5,6]; let newArr = arr.filter(function(item,index,origin){ console.log(typeof this,this);//object,String {"zhuangzhuang"} return item > 3; },'zhuangzhuang') console.log(newArr);//[4,5,6]

两者对比,可以发现其实是一模一样的,说明咱们的改变this指向的原理是正确的。

2.origin正在处理的数组(原始数组)
let arr = [1,2,3,4,5,6]; let newArr = arr.filter(function(item,index,origin){ origin[0] = origin[0] + 100; return item > 3; },'zhuangzhuang') console.log(newArr,arr);//[4, 5, 6] ,[601, 2, 3, 4, 5, 6]

从打印的结果来看,origin跟arr其实是引用关系,那么来看下我们写的原理是否是引用关系呢?

Array.prototype.filtersss = function(filterFn,thisArg){ let newArr = []; for(var i = 0;i<this.length;i++){ filterFn.call(thisArg,this[i],i,this) && newArr.push(this[i]); } return newArr; } let arr = [1,2,3,4,5,6]; let newArr = arr.filtersss(function(item,index,origin){ origin[0] = origin[0] + 100; return item > 3; },'zhuangzhuang') console.log(newArr);//[4, 5, 6] ,[601, 2, 3, 4, 5, 6]

结果跟原生的一模一样,可见,咱们的origin参数也是实现了。


看到这里,可能有同学没有耐心的会说一句:这么简单的东西,你要写这么长的篇幅。然后关掉了网页。对于这些同学,我向你深深鞠一躬,因为我上面写的,其实都是有问题的(流汗,鞠躬,奸笑)。 那么,为了纠正还在继续耐心看的同学,我再写一个例子,应该就能很快明白

let arr = [1,2,3,4,5,6]; let newArr = arr.filter(function(item,index,origin){ origin[1] = origin[1] + 100;//仅仅只是把0改成1 return item > 3; },'zhuangzhuang') console.log(newArr,arr);//[102, 4, 5, 6] , [1, 602, 3, 4, 5, 6]

按照我们写的原理来执行一遍:

Array.prototype.filtersss = function(filterFn,thisArg){ let newArr = []; for(var i = 0;i<this.length;i++){ filterFn.call(thisArg,this[i],i,this) && newArr.push(this[i]); } return newArr; } let arr = [1,2,3,4,5,6]; let newArr = arr.filtersss(function(item,index,origin){ origin[1] = origin[1] + 100;//把0改成1 //a行 return item > 3; //b行 },'zhuangzhuang') console.log(newArr);//[202, 4, 5, 6] ,[1, 602, 3, 4, 5, 6]

很明显,结果不一样了…为什么? 按照我们的原理:

当i为0的时候,callback传入的参数为1,0,[1,2,3,4,5,6]; a行:origin[1]为1]2+100,为101,这时候arr为[1,102,3,4,5,6]b行:item为1

当i为1的时候,callback传入的参数为102,1,[1,102,3,4,5,6];

a行:origin[1]为102+100,为202,这时候arr为[1,202,3,4,5,6]

b行:item为102

思考:咦?不是102吗?为什么按照原理打印出来会是202,因为其实别忘了原理中传入的是this[i]跟this的关系是什么?是引用关系!那么,改变this,自然就改变了this[i],而这里的this[i]就是item。

所以,我们的原理为了保证跟es6中的filter一模一样,必须考虑到”引用”这一点。所以,我们来改写这个原理:

Array.prototype.filter = function(filterFn,thisArg){ let newArr = []; for(var i = 0;i<this.length;i++){ let _this = this.slice(0);//注意,务必要先复制一份 filterFn.call(thisArg,_this[i],i,this) && newArr.push(_this[i]); } return newArr; } let arr = [1,2,3,4,5,6]; let newArr = arr.filter(function(item,index,origin){ origin[1] = origin[1] + 100; console.log(origin[1]); return item > 3; },'zhuangzhuang') console.log(newArr,arr);//[102,4, 5, 6] ,[1, 602, 3, 4, 5, 6]

我们必须要在传入之前赋值一份当前的数组,这样,才能保证item其实是原有的item,而不是因为”引用”被修改过的item。 由此可见,其实,我们在遍历的每一项是arr本身的每一项吗?并不是的!而是副本!仅仅只是副本而已!


思考: 如果把let _this = this.slice(0);这句代码,提到for循环之前,会产生什么效果?这样改写跟es6的filter有什么区别?适合应用到哪些场景? filter是这样的原理,那么数组的其他方法呢?forEach、find、findIndex…


总结: ES6中filter的原理如下:

Array.prototype.filter = function(filterFn,thisArg){ let newArr = []; for(var i = 0;i<this.length;i++){ let _this = this.slice(0);//注意,务必要先复制一份 filterFn.call(thisArg,_this[i],i,this) && newArr.push(_this[i]); } return newArr; }

PS:如有未考虑到之处,请联系微信809742006

转载请注明原文地址: https://www.6miu.com/read-3450246.html

最新回复(0)