天天看點

你以為arr.filter周遊的每一項,真的是arr的每一項嗎?

想必大家一定用慣了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

你以為arr.filter周遊的每一項,真的是arr的每一項嗎?
可見,filter是有兩個參數的,第一個參數是callback,第二個參數是thisArg
  1. callback:周遊數組時,處理每一項的處理函數
    1. 參數:
      • element:正在進行中的數組的目前項,即上例中的item
      • index:目前正在進行中的數組的目前項的索引,即上例中的item
      • array:正在處理的數組
  2. thisArg:改變正在執行的callback中的this指向為thisArg
  3. 傳回值:當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]
           

很明顯,結果不一樣了…為什麼?

按照我們的原理:

  1. 當i為0的時候,callback傳入的參數為1,0,[1,2,3,4,5,6];
    1. a行:origin[1]為1]2+100,為101,這時候arr為[1,102,3,4,5,6]
    2. b行:item為1
  2. 當i為1的時候,callback傳入的參數為102,1,[1,102,3,4,5,6];
    1. a行:origin[1]為102+100,為202,這時候arr為[1,202,3,4,5,6]
    2. 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