JavaScript遍歷方法總結與對比

八叉樹發表於2019-03-30

引言

首先需要知道對於陣列和可迭代物件的遍歷方法,我們需要從不同的維度進行對比,方法的功能性,方法的應用場景,方法的相容性,方法的效率,方法的返回值以及是否改變原始陣列。深層次的我們可以思考如何實現這些方法,並且考慮到低版本瀏覽器的相容性。如果要分group的話,可以這麼分:forEach()與map()every()與some()filter()與find()findIndex(),keys()、values()與entries(),reduce()與reduceRight()。當然對於的常規的遍歷方法,比如forwhiledo-while,$.each,$(selector).each這裡就不贅述了,原生的方法當然是主角,但也要看應用場景,專案中使用新增的遍歷方法確實能提高程式碼效率,jquery的方法當然也有借鑑的地方,但是在現在的前端專案中用到的較少。

JavaScript遍歷方法總結與對比

forEach ()

指定陣列的每一項元素都執行了一次傳入的函式,返回值為undefined,thisArg可選,用來當做fn函式內的this物件;forEach不能在低版本IE(6~8)中使用,相容寫法請參考 Polyfill。得益於鴨式辨型,雖然forEach不能直接遍歷物件,但它可以通過call方式遍歷類陣列物件

var o = {0:1, 1:3, 2:5, length:3};
Array.prototype.forEach.call(o,function(value, index, obj){
  console.log(value,index,obj);
  obj[index] = value * value;
},o);
// 1 0 Object {0: 1, 1: 3, 2: 5, length: 3}
// 3 1 Object {0: 1, 1: 3, 2: 5, length: 3}
// 5 2 Object {0: 1, 1: 9, 2: 5, length: 3}
console.log(o); // Object {0: 1, 1: 9, 2: 25, length: 3}
複製程式碼

可以說這麼多遍歷函式中,forEach()要注意的地方最多,有以下要注意的:

  • this都是指向呼叫方法的陣列;

    var array = [1, 3, 5];
    var obj = {name:'cc'};
    var sReturn = array.forEach(function(value, index, array){
      array[index] = value * value;
      console.log(this.name); // cc被列印了三次
    },obj);
    console.log(array); // [1, 9, 25], 可見原陣列改變了
    console.log(sReturn); // undefined, 可見返回值為undefined
    複製程式碼
  • 沒有返回值,它總是返回undefined值,即使你return了一個值

    var solt = arr1.forEach((v,i,t) => {
      console.log(v)
    })
    
    console.log(solt)	// undefined
    複製程式碼
  • 不能 中途退出迴圈,不能用break,會報錯的,只能用return退出本次回撥,進行下一次回撥

    var arr1 = [1, 2, 3, 4, 5]
    
    // 使用break會報錯
    arr1.forEach((v,i,arr) => {
      console.log(v)
      if(v === 3) {
        break
      }
    })
    //SyntaxError: Illegal break statement
    
    
    
    // return false 也無效
    arr1.forEach((v,i,arr) => {
      console.log(v)
      if(v === 3) {
        return false
        console.log('-----')//不會執行
      }
    })
    /*
    1
    2
    3
    4
    5
    */
    複製程式碼
  • 使用箭頭函式,thisArg 引數會被忽略

    var arr1 = [1, 2, 3]
    var arr2 = [7, 8, 9]
    
    arr1.forEach((v, i, arr) => {
      console.log(this)
    })
    // window
    // window
    // window
    
    arr1.forEach((v, i, arr) => {
      console.log(this)
    }, arr2)
    // window
    // window
    // window
    複製程式碼

map()

map() 方法遍歷陣列,使用傳入函式處理每個元素,並返回函式的返回值組成的新陣列。其在低版本IE(6~8)的相容寫法請參考 Polyfill

every()與some()

every() 方法使用傳入的函式測試所有元素,只要其中有一個函式返回值為 false,那麼該方法的結果為 false;如果全部返回 true,那麼該方法的結果才為 true;every 一樣不能在低版本IE(6~8)中使用,相容寫法請參考 Polyfill

以下是鴨式辨型的寫法:

var o = {0:10, 1:8, 2:25, length:3};
var bool = Array.prototype.every.call(o,function(value, index, obj){
  return value >= 8;
},o);
console.log(bool); // true
複製程式碼

some()方法 測試陣列元素時,只要有一個函式返回值為 true,則該方法返回 true,若全部返回 false,則該方法返回 false;some 的鴨式辨型寫法可以參照every,同樣它也不能在低版本IE(6~8)中使用,相容寫法請參考 Polyfill

filter()

filter() 方法使用傳入的函式測試所有元素,並返回所有通過測試的元素組成的新陣列。它就好比一個過濾器,篩掉不符合條件的元素。filter一樣支援鴨式辨型,具體請參考every方法鴨式辨型寫法。其在低版本IE(6~8)的相容寫法請參考 Polyfill

var array = [18, 9, 10, 35, 80];
var array2 = array.filter(function(value, index, array){
  return value > 20;
});
console.log(array2); // [35, 80]
複製程式碼

for-in 語句

一般會使用for-in來遍歷物件的屬性的,不過屬性需要 enumerable,才能被讀取到. for-in 迴圈只遍歷可列舉屬性。一般常用來遍歷物件,包括非整數型別的名稱和繼承的那些原型鏈上面的屬性也能被遍歷。像 Array和 Object使用內建建構函式所建立的物件都會繼承自Object.prototype和String.prototype的不可列舉屬性就不能遍歷了

var obj = {
    name: 'test',
    color: 'red',
    day: 'sunday',
    number: 5
}
for (var key in obj) {
    console.log(obj[key])
}
複製程式碼

for-of 語句

for-of語句在可迭代物件(包括 Array,Map,Set,String,TypedArray,arguments 物件等等)上建立一個迭代迴圈,呼叫自定義迭代鉤子,併為每個不同屬性的值執行語句。只要是一個iterable的物件,就可以通過for-of來迭代.

var arr = [{name:'bb'},5,'test']
for (item of arr) {
    console.log(item)
}
複製程式碼

find()與findIndex()

find() 方法基於ECMAScript 2015(ES6)規範,返回陣列中第一個滿足條件的元素(如果有的話), 如果沒有,則返回undefined。

findIndex() 方法也基於ECMAScript 2015(ES6)規範,它返回陣列中第一個滿足條件的元素的索引(如果有的話)否則返回-1。

var array = [1, 3, 5, 7, 8, 9, 10];
function f(value, index, array){
  return value%2==0; // 返回偶數
}
function f2(value, index, array){
  return value > 20; // 返回大於20的數
}
console.log(array.find(f)); // 8
console.log(array.find(f2)); // undefined
console.log(array.findIndex(f)); // 4
console.log(array.findIndex(f2)); // -1
複製程式碼

includes()

判斷一個陣列是否包含一個指定的值,如果包含則返回 true,否則返回false。

var array1 = [1, 2, 3];

console.log(array1.includes(2));
// expected output: true

var pets = ['cat', 'dog', 'bat'];

console.log(pets.includes('cat'));
// expected output: true

console.log(pets.includes('at'));
// expected output: false
複製程式碼

reduce()與reduceRight()

reduce() 方法接收一個方法作為累加器,陣列中的每個值(從左至右) 開始合併,最終為一個值;initialValue 指定第一次呼叫 fn 的第一個引數。;當 fn 第一次執行時:

  • 如果 initialValue 在呼叫 reduce 時被提供,那麼第一個 previousValue 將等於 initialValue,此時 item 等於陣列中的第一個值;
  • 如果 initialValue 未被提供,那麼 previousVaule 等於陣列中的第一個值,item 等於陣列中的第二個值。此時如果陣列為空,那麼將丟擲 TypeError。
  • 如果陣列僅有一個元素,並且沒有提供 initialValue,或提供了 initialValue 但陣列為空,那麼fn不會被執行,陣列的唯一值將被返回。
var array = [1, 2, 3, 4];
var s = array.reduce(function(previousValue, value, index, array){
  return previousValue * value;
},1);
console.log(s); // 24
// ES6寫法更加簡潔
array.reduce((p, v) => p * v); // 24
複製程式碼

reduceRight() 方法接收一個方法作為累加器,陣列中的每個值(從右至左)開始合併,最終為一個值。除了與reduce執行方向相反外,其他完全與其一致.

entries()

entries() 方法基於ECMAScript 2015(ES6)規範,返回一個陣列迭代器物件,該物件包含陣列中每個索引的鍵值對。

var array = ["a", "b", "c"];
var iterator = array.entries();
console.log(iterator.next().value); // [0, "a"]
console.log(iterator.next().value); // [1, "b"]
console.log(iterator.next().value); // [2, "c"]
console.log(iterator.next().value); // undefined, 迭代器處於陣列末尾時, 再迭代就會返回undefined
複製程式碼

entries 也受益於鴨式辨型,如下:

var o = {0:"a", 1:"b", 2:"c", length:3};
var iterator = Array.prototype.entries.call(o);
console.log(iterator.next().value); // [0, "a"]
console.log(iterator.next().value); // [1, "b"]
console.log(iterator.next().value); // [2, "c"]
複製程式碼

keys()

keys() 方法基於ECMAScript 2015(ES6)規範,返回一個陣列索引的迭代器。(瀏覽器實際實現可能會有調整)

var array = ["abc", "xyz"];
var iterator = array.keys();
console.log(iterator.next()); // Object {value: 0, done: false}
console.log(iterator.next()); // Object {value: 1, done: false}
console.log(iterator.next()); // Object {value: undefined, done: false}
複製程式碼

要注意的是索引迭代器會包含那些沒有對應元素的索引

var array = ["abc", , "xyz"];
var sparseKeys = Object.keys(array);
var denseKeys = [...array.keys()];
console.log(sparseKeys); // ["0", "2"]
console.log(denseKeys);  // [0, 1, 2]
複製程式碼

values()

values() 方法基於ECMAScript 2015(ES6)規範,返回一個陣列迭代器物件,該物件包含陣列中每個索引的值。其用法基本與上述 entries 方法一致。相容性方面,目前沒有瀏覽器實現了該方法

var array = ["abc", "xyz"];
var iterator = array.values();
console.log(iterator.next().value);//abc
console.log(iterator.next().value);//xyz
複製程式碼

Symbol.iterator()

該方法基於ECMAScript 2015(ES6)規範,同 values 方法功能相同。

var array = ["abc", "xyz"];
var iterator = array[Symbol.iterator]();
console.log(iterator.next().value); // abc
console.log(iterator.next().value); // xyz      
複製程式碼

總結

陣列遍歷的方法中

  • 返回值有的是陣列,有的是一個值,有的是布林值,有的是一個陣列迭代器物件;

  • every,some是用來判斷的,返回一個布林值;filter,find,findIndex都是用來篩選的,並且返回滿足條件的元素或元素組成的陣列,不同的是,filter返回所有滿足條件的陣列成的陣列,find與findIndex返回第一個滿足條件的元素或元素的索引;

  • Array.prototype的所有方法均具有鴨式辨型這種神奇的特性,它們不僅可以用來處理陣列物件,還可以處理類陣列物件;

  • ES6中的新增方法應用物件更廣泛,可以用於所有的具有Iterator介面的可迭代物件,比如陣列,類陣列物件,Map和Set結構;

  • 對於各種遍歷方法的效率問題,這篇文章(詳解JS遍歷)對JS中遍歷方法進行了測試比較,得出的結論就是JavaScript原生的方法的效率高於各種封裝的方法;

  • forEach()與map()的不同點:

    • map()建立了新陣列,不改變原陣列;forEach()可以改變原陣列。

    • 遇到空缺的時候map()雖然會跳過,但保留空缺;forEach()遍歷時跳過空缺,不保留空缺。

    • map()按照原始陣列元素順序依次處理元素;forEach()遍歷陣列的每個元素,將元素傳給回撥函式。

    forEach()為陣列中的每個元素新增屬性

    var arr = [
        {name : 'kiwi', age : 12},
        {name : 'sasa', age : 22},
        {name : 'alice', age : 32},
        {name : 'joe', age : 42}
    ]
    arr.forEach(function(ele, index){
        if(index > 2){
            ele.sex = 'boy';
        }else{
            ele.sex = 'girl';
        }
        return arr1
    })
    console.log(arr)//元素組發生改變
    //[{name: "kiwi", age: 12, sex: "girl"},{name: "sasa", age: 22, sex: "girl"},{name: "alice", age: 32, sex: "gi
    複製程式碼

    ** 遇到空缺比較**

    ['a', , 'b'].forEach(function(ele,index){
        console.log(ele + '. ' + index);
    })
    //0.a
    //2.b
    ['a', , 'b'].map(function(ele,index){
        console.log(ele + '. ' + index);
    })
    //['0.a', , '2.b']
    複製程式碼
  • for-of與for-in的不同點

    • for...of迴圈不會迴圈物件的key,只會迴圈出陣列的value,因此for...of不能迴圈遍歷普通物件,對普通物件的屬性遍歷推薦使用for...in。如果實在想用for...of來遍歷普通物件的屬性的話,可以通過和Object.keys()搭配使用,先獲取物件的所有key的陣列然後遍歷:
    var student={
        name:'wujunchuan',
        age:22,
        locate:{
        country:'china',
        city:'xiamen',
        school:'XMUT'
        }
    }
    for(var key of Object.keys(student)){
        //使用Object.keys()方法獲取物件key的陣列
        console.log(key+": "+student[key]);
    }
    /*
    //如果是下面這樣,會報錯提示 “TypeError: student is not iterable”
    for(var key of student){
        //使用Object.keys()方法獲取物件key的陣列
        console.log(key+": "+student[key]);
    }
    */
    複製程式碼
    • for...in迴圈出的是key,for...of迴圈出的是value;推薦在迴圈物件屬性的時候,使用for...in,在遍歷陣列的時候的時候使用for...of。注意,for...of是ES6新引入的特性。修復了ES5引入的for...in的不足

參考:

  1. 深度長文】JavaScript陣列所有API全解密
  2. 【乾貨】js 陣列詳細操作方法及解析合集
  3. 詳解JS遍歷

相關文章