for...of 循環(ES6)

ES6 借鑑 C++、Java、C# 和 Python 語言,引入了for...of循環,作爲遍歷所有數據結構的統一的方法。

一個數據結構只要部署了Symbol.iterator屬性,就被視爲具有 iterator 接口,就可以用for...of循環遍歷它的成員。也就是說,for...of循環內部調用的是數據結構的Symbol.iterator方法。

for...of循環可以使用的範圍包括數組、Set 和 Map 結構、某些類似數組的對象(比如arguments對象、DOM NodeList 對象)、後文的 Generator 對象,以及字符串。

數組

數組原生具備iterator接口(即默認部署了Symbol.iterator屬性),for...of循環本質上就是調用這個接口產生的遍歷器,可以用下面的代碼證明。

const arr = ['red', 'green', 'blue'];

for(let v of arr) {
  console.log(v); // red green blue
}

const obj = {};
obj[Symbol.iterator] = arr[Symbol.iterator].bind(arr);

for(let v of obj) {
  console.log(v); // red green blue
}

上面代碼中,空對象obj部署了數組arrSymbol.iterator屬性,結果objfor...of循環,產生了與arr完全一樣的結果。

for...of循環可以代替數組實例的forEach方法。

const arr = ['red', 'green', 'blue'];

arr.forEach(function (element, index) {
  console.log(element); // red green blue
  console.log(index);   // 0 1 2
});

JavaScript 原有的for...in循環,只能獲得對象的鍵名,不能直接獲取鍵值。ES6 提供for...of循環,允許遍歷獲得鍵值。

var arr = ['a', 'b', 'c', 'd'];

for (let a in arr) {
  console.log(a); // 0 1 2 3
}

for (let a of arr) {
  console.log(a); // a b c d
}

上面代碼表明,for...in循環讀取鍵名,for...of循環讀取鍵值。如果要通過for...of循環,獲取數組的索引,可以藉助數組實例的entries方法和keys方法。

for...of循環調用遍歷器接口,數組的遍歷器接口只返回具有數字索引的屬性。這一點跟for...in循環也不一樣。

 

let arr = [3, 5, 7];
arr.foo = 'hello';

for (let i in arr) {
  console.log(i); // "0", "1", "2", "foo"
}

for (let i of arr) {
  console.log(i); //  "3", "5", "7"
}

 

上面代碼中,for...of循環不會返回數組arrfoo屬性。

Set 和 Map 結構

Set 和 Map 結構也原生具有 Iterator 接口,可以直接使用for...of循環。

var engines = new Set(["Gecko", "Trident", "Webkit", "Webkit"]);
for (var e of engines) {
  console.log(e);
}
// Gecko
// Trident
// Webkit

var es6 = new Map();
es6.set("edition", 6);
es6.set("committee", "TC39");
es6.set("standard", "ECMA-262");
for (var [name, value] of es6) {
  console.log(name + ": " + value);
}
// edition: 6
// committee: TC39
// standard: ECMA-262

上面代碼演示瞭如何遍歷 Set 結構和 Map 結構。值得注意的地方有兩個,首先,遍歷的順序是按照各個成員被添加進數據結構的順序。其次,Set 結構遍歷時,返回的是一個值,而 Map 結構遍歷時,返回的是一個數組,該數組的兩個成員分別爲當前 Map 成員的鍵名和鍵值。

let map = new Map().set('a', 1).set('b', 2);
for (let pair of map) {
  console.log(pair);
}
// ['a', 1]
// ['b', 2]

for (let [key, value] of map) {
  console.log(key + ' : ' + value);
}
// a : 1
// b : 2

計算生成的數據結構

有些數據結構是在現有數據結構的基礎上,計算生成的。比如,ES6 的數組、Set、Map 都部署了以下三個方法,調用後都返回遍歷器對象。

  • entries() 返回一個遍歷器對象,用來遍歷[鍵名, 鍵值]組成的數組。對於數組,鍵名就是索引值;對於 Set,鍵名與鍵值相同。Map 結構的 Iterator 接口,默認就是調用entries方法。
  • keys() 返回一個遍歷器對象,用來遍歷所有的鍵名。
  • values() 返回一個遍歷器對象,用來遍歷所有的鍵值。

這三個方法調用後生成的遍歷器對象,所遍歷的都是計算生成的數據結構。

let arr = ['a', 'b', 'c'];
for (let pair of arr.entries()) {
  console.log(pair);
}
// [0, 'a']
// [1, 'b']
// [2, 'c']

類似數組的對象

類似數組的對象包括好幾類。下面是for...of循環用於字符串、DOM NodeList 對象、arguments對象的例子。

// 字符串
let str = "hello";

for (let s of str) {
  console.log(s); // h e l l o
}

// DOM NodeList對象
let paras = document.querySelectorAll("p");

for (let p of paras) {
  p.classList.add("test");
}

// arguments對象
function printArgs() {
  for (let x of arguments) {
    console.log(x);
  }
}
printArgs('a', 'b');
// 'a'
// 'b'

對於字符串來說,for...of循環還有一個特點,就是會正確識別 32 位 UTF-16 字符。

for (let x of 'a\uD83D\uDC0A') {
  console.log(x);
}
// 'a'
// '\uD83D\uDC0A'

並不是所有類似數組的對象都具有 Iterator 接口,一個簡便的解決方法,就是使用Array.from方法將其轉爲數組。

let arrayLike = { length: 2, 0: 'a', 1: 'b' };

// 報錯
for (let x of arrayLike) {
  console.log(x);
}

// 正確
for (let x of Array.from(arrayLike)) {
  console.log(x);
}

對象

對於普通的對象,for...of結構不能直接使用,會報錯,必須部署了 Iterator 接口後才能使用。但是,這樣情況下,for...in循環依然可以用來遍歷鍵名。

let es6 = {
  edition: 6,
  committee: "TC39",
  standard: "ECMA-262"
};

for (let e in es6) {
  console.log(e);
}
// edition
// committee
// standard

for (let e of es6) {
  console.log(e);
}
// TypeError: es6[Symbol.iterator] is not a function

上面代碼表示,對於普通的對象,for...in循環可以遍歷鍵名,for...of循環會報錯。

一種解決方法是,使用Object.keys方法將對象的鍵名生成一個數組,然後遍歷這個數組。

for (var key of Object.keys(someObject)) {
  console.log(key + ': ' + someObject[key]);
}

另一個方法是使用 Generator 函數將對象重新包裝一下。

function* entries(obj) {
  for (let key of Object.keys(obj)) {
    yield [key, obj[key]];
  }
}

for (let [key, value] of entries(obj)) {
  console.log(key, '->', value);
}
// a -> 1
// b -> 2
// c -> 3

與其他遍歷語法的比較

以數組爲例,JavaScript 提供多種遍歷語法。最原始的寫法就是for循環。

for (var index = 0; index < myArray.length; index++) {
  console.log(myArray[index]);
}

這種寫法比較麻煩,因此數組提供內置的forEach方法。

myArray.forEach(function (value) {
  console.log(value);
});

這種寫法的問題在於,無法中途跳出forEach循環,break命令或return命令都不能奏效。

for...in循環可以遍歷數組的鍵名。

for (var index in myArray) {
  console.log(myArray[index]);
}

for...in循環有幾個缺點。

  • 數組的鍵名是數字,但是for...in循環是以字符串作爲鍵名“0”、“1”、“2”等等。
  • for...in循環不僅遍歷數字鍵名,還會遍歷手動添加的其他鍵,甚至包括原型鏈上的鍵。
  • 某些情況下,for...in循環會以任意順序遍歷鍵名。

總之,for...in循環主要是爲遍歷對象而設計的,不適用於遍歷數組。

for...of循環相比上面幾種做法,有一些顯著的優點。

for (let value of myArray) {
  console.log(value);
}
  • 有着同for...in一樣的簡潔語法,但是沒有for...in那些缺點。
  • 不同於forEach方法,它可以與breakcontinuereturn配合使用。
  • 提供了遍歷所有數據結構的統一操作接口。

下面是一個使用 break 語句,跳出for...of循環的例子。

for (var n of fibonacci) {
  if (n > 1000)
    break;
  console.log(n);
}

上面的例子,會輸出斐波納契數列小於等於 1000 的項。如果當前項大於 1000,就會使用break語句跳出for...of循環。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章