想必大家一定用慣了es6中的各類文法,尤其是數組提供的方法,更是讓我們對數組的處理獲得了極大的便利。
今天我要講的其實是一些數組中非常簡單的方法,但當你真的研究它的時候,你會發現一些奇妙之處。
一、初溫數組的filter方法
let arr = [,,,,,];
let newArr = arr.filter(function(item,index){
return item > ;
})
console.log(newArr);//[4,5,6]
很簡單,當return為true的時候,會傳回該項,所有傳回的項會組成一個新的數組
那麼,這個filter到底是怎麼實作的呢?我們還是按照老習慣,把它用es5實作出來(如果你看過我其他的文章,應該習慣用es5來介紹es6,這有助于了解es6的文法)
Array.prototype.filter = function(filterFn){
let newArr = [];
for(var i = ;i<this.length;i++){
filterFn(this[i],i) && newArr.push(this[i]);
}
return newArr;
}
let arr = [,,,,,];
let newArr = arr.filter(function(item,index){
return item > ;
})
可以拷貝下這段代碼去執行下,你會發現跟原生filter結果一模一樣。
原理也很簡單,周遊該數組,将該數組的每一項傳入callback,當滿足callback傳回值為true的時候,将該項放入新數組中,然後傳回這個新數組即可。
其實,這隻是一個閹割版的filter,讓我們看下MDN是如何實作這個filter的
二、完整版filter
可見,filter是有兩個參數的,第一個參數是callback,第二個參數是thisArg
- callback:周遊數組時,處理每一項的處理函數
- 參數:
- element:正在進行中的數組的目前項,即上例中的item
- index:目前正在進行中的數組的目前項的索引,即上例中的item
- array:正在處理的數組
- 參數:
- 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 = ;i<this.length;i++){
filterFn.call(thisArg,this[i],i,this) && newArr.push(this[i]);
}
return newArr;
}
let arr = [,,,,,];
let newArr = arr.filtersss(function(item,index,origin){
return item > ;
})
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 = ;i<this.length;i++){
filterFn.call(thisArg,this[i],i,this) && newArr.push(this[i]);
}
return newArr;
}
let arr = [,,,,,];
let newArr = arr.filtersss(function(item,index,origin){
console.log(typeof this,this);//object,String {"zhuangzhuang"}
return item > ;
},'zhuangzhuang')
console.log(newArr);//[4,5,6]
那麼我們原生的filter是怎麼樣的呢?
let arr = [,,,,,];
let newArr = arr.filter(function(item,index,origin){
console.log(typeof this,this);//object,String {"zhuangzhuang"}
return item > ;
},'zhuangzhuang')
console.log(newArr);//[4,5,6]
兩者對比,可以發現其實是一模一樣的,說明咱們的改變this指向的原理是正确的。
2.origin正在處理的數組(原始數組)
let arr = [,,,,,];
let newArr = arr.filter(function(item,index,origin){
origin[] = origin[] + ;
return item > ;
},'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 = ;i<this.length;i++){
filterFn.call(thisArg,this[i],i,this) && newArr.push(this[i]);
}
return newArr;
}
let arr = [,,,,,];
let newArr = arr.filtersss(function(item,index,origin){
origin[] = origin[] + ;
return item > ;
},'zhuangzhuang')
console.log(newArr);//[4, 5, 6] ,[601, 2, 3, 4, 5, 6]
結果跟原生的一模一樣,可見,咱們的origin參數也是實作了。
看到這裡,可能有同學沒有耐心的會說一句:這麼簡單的東西,你要寫這麼長的篇幅。然後關掉了網頁。對于這些同學,我向你深深鞠一躬,因為我上面寫的,其實都是有問題的(流汗,鞠躬,奸笑)。
那麼,為了糾正還在繼續耐心看的同學,我再寫一個例子,應該就能很快明白
let arr = [,,,,,];
let newArr = arr.filter(function(item,index,origin){
origin[] = origin[] + ;//僅僅隻是把0改成1
return item > ;
},'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 = ;i<this.length;i++){
filterFn.call(thisArg,this[i],i,this) && newArr.push(this[i]);
}
return newArr;
}
let arr = [,,,,,];
let newArr = arr.filtersss(function(item,index,origin){
origin[] = origin[] + ;//把0改成1 //a行
return item > ; //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 = ;i<this.length;i++){
let _this = this.slice();//注意,務必要先複制一份
filterFn.call(thisArg,_this[i],i,this) && newArr.push(_this[i]);
}
return newArr;
}
let arr = [,,,,,];
let newArr = arr.filter(function(item,index,origin){
origin[] = origin[] + ;
console.log(origin[]);
return item > ;
},'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 = ;i<this.length;i++){
let _this = this.slice();//注意,務必要先複制一份
filterFn.call(thisArg,_this[i],i,this) && newArr.push(_this[i]);
}
return newArr;
}
PS:如有未考慮到之處,請聯系微信809742006