ES6之路第十三篇:Iterator和for...of循環

Iterator(遍歷器)的概念

JavaScript 原有的表示“集合”的數據結構,主要是數組(Array)和對象(Object),ES6 又添加了MapSet。這樣就有了四種數據集合,用戶還可以組合使用它們,定義自己的數據結構,比如數組的成員是MapMap的成員是對象。這樣就需要一種統一的接口機制,來處理所有不同的數據結構。

遍歷器(Iterator)就是這樣一種機制。它是一種接口,為各種不同的數據結構提供統一的訪問機制。任何數據結構只要部署 Iterator 接口,就可以完成遍歷操作(即依次處理該數據結構的所有成員)。

Iterator 的作用有三個:一是為各種數據結構,提供一個統一的、簡便的訪問接口;二是使得數據結構的成員能夠按某種次序排列;三是 ES6 創造了一種新的遍歷命令for...of循環,Iterator 接口主要供for...of消費。

Iterator 的遍歷過程是這樣的。

(1)創建一個指針對象,指向當前數據結構的起始位置。也就是說,遍歷器對象本質上,就是一個指針對象。

(2)第一次調用指針對象的next方法,可以將指針指向數據結構的第一個成員。

(3)第二次調用指針對象的next方法,指針就指向數據結構的第二個成員。

(4)不斷調用指針對象的next方法,直到它指向數據結構的結束位置。

每一次調用next方法,都會返回數據結構的當前成員的信息。具體來說,就是返回一個包含valuedone兩個屬性的對象。其中,value屬性是當前成員的值,done屬性是一個布爾值,表示遍歷是否結束。

下面是一個模擬next方法返回值的例子。

 1 var it = makeIterator(['a', 'b']);
 2 
 3 it.next() // { value: "a", done: false }
 4 it.next() // { value: "b", done: false }
 5 it.next() // { value: undefined, done: true }
 6 
 7 function makeIterator(array) {
 8   var nextIndex = 0;
 9   return {
10     next: function() {
11       return nextIndex < array.length ?
12         {value: array[nextIndex++], done: false} :
13         {value: undefined, done: true};
14     }
15   };
16 }

上面代碼定義了一個makeIterator函數,它是一個遍歷器生成函數,作用就是返回一個遍歷器對象。對數組['a', 'b']執行這個函數,就會返回該數組的遍歷器對象(即指針對象)it

指針對象的next方法,用來移動指針。開始時,指針指向數組的開始位置。然后,每次調用next方法,指針就會指向數組的下一個成員。第一次調用,指向a;第二次調用,指向b

next方法返回一個對象,表示當前數據成員的信息。這個對象具有valuedone兩個屬性,value屬性返回當前位置的成員,done屬性是一個布爾值,表示遍歷是否結束,即是否還有必要再一次調用next方法。

總之,調用指針對象的next方法,就可以遍歷事先給定的數據結構。

對于遍歷器對象來說,done: falsevalue: undefined屬性都是可以省略的,因此上面的makeIterator函數可以簡寫成下面的形式。

 1 function makeIterator(array) {
 2   var nextIndex = 0;
 3   return {
 4     next: function() {
 5       return nextIndex < array.length ?
 6         {value: array[nextIndex++]} :
 7         {done: true};
 8     }
 9   };
10 }

由于 Iterator 只是把接口規格加到數據結構之上,所以,遍歷器與它所遍歷的那個數據結構,實際上是分開的,完全可以寫出沒有對應數據結構的遍歷器對象,或者說用遍歷器對象模擬出數據結構。下面是一個無限運行的遍歷器對象的例子。

 1 var it = idMaker();
 2 
 3 it.next().value // 0
 4 it.next().value // 1
 5 it.next().value // 2
 6 // ...
 7 
 8 function idMaker() {
 9   var index = 0;
10 
11   return {
12     next: function() {
13       return {value: index++, done: false};
14     }
15   };
16 }

上面的例子中,遍歷器生成函數idMaker,返回一個遍歷器對象(即指針對象)。但是并沒有對應的數據結構,或者說,遍歷器對象自己描述了一個數據結構出來。

如果使用 TypeScript 的寫法,遍歷器接口(Iterable)、指針對象(Iterator)和next方法返回值的規格可以描述如下。

 1 interface Iterable {
 2   [Symbol.iterator]() : Iterator,
 3 }
 4 
 5 interface Iterator {
 6   next(value?: any) : IterationResult,
 7 }
 8 
 9 interface IterationResult {
10   value: any,
11   done: boolean,
12 }

默認Iterator接口

Iterator 接口的目的,就是為所有數據結構,提供了一種統一的訪問機制,即for...of循環(詳見下文)。當使用for...of循環遍歷某種數據結構時,該循環會自動去尋找 Iterator 接口。

一種數據結構只要部署了 Iterator 接口,我們就稱這種數據結構是“可遍歷的”(iterable)。

ES6 規定,默認的 Iterator 接口部署在數據結構的Symbol.iterator屬性,或者說,一個數據結構只要具有Symbol.iterator屬性,就可以認為是“可遍歷的”(iterable)。Symbol.iterator屬性本身是一個函數,就是當前數據結構默認的遍歷器生成函數。執行這個函數,就會返回一個遍歷器。至于屬性名Symbol.iterator,它是一個表達式,返回Symbol對象的iterator屬性,這是一個預定義好的、類型為 Symbol 的特殊值,所以要放在方括號內。

 1 const obj = {
 2   [Symbol.iterator] : function () {
 3     return {
 4       next: function () {
 5         return {
 6           value: 1,
 7           done: true
 8         };
 9       }
10     };
11   }
12 };

上面代碼中,對象obj是可遍歷的(iterable),因為具有Symbol.iterator屬性。執行這個屬性,會返回一個遍歷器對象。該對象的根本特征就是具有next方法。每次調用next方法,都會返回一個代表當前成員的信息對象,具有valuedone兩個屬性。

ES6 的有些數據結構原生具備 Iterator 接口(比如數組),即不用任何處理,就可以被for...of循環遍歷。原因在于,這些數據結構原生部署了Symbol.iterator屬性(詳見下文),另外一些數據結構沒有(比如對象)。凡是部署了Symbol.iterator屬性的數據結構,就稱為部署了遍歷器接口。調用這個接口,就會返回一個遍歷器對象。

原生具備 Iterator 接口的數據結構如下。

  • Array
  • Map
  • Set
  • String
  • TypedArray
  • 函數的 arguments 對象
  • NodeList 對象

下面的例子是數組的Symbol.iterator屬性。

1 let arr = ['a', 'b', 'c'];
2 let iter = arr[Symbol.iterator]();
3 
4 iter.next() // { value: 'a', done: false }
5 iter.next() // { value: 'b', done: false }
6 iter.next() // { value: 'c', done: false }
7 iter.next() // { value: undefined, done: true }

上面代碼中,變量arr是一個數組,原生就具有遍歷器接口,部署在arrSymbol.iterator屬性上面。所以,調用這個屬性,就得到遍歷器對象。

對于原生部署 Iterator 接口的數據結構,不用自己寫遍歷器生成函數,for...of循環會自動遍歷它們。除此之外,其他數據結構(主要是對象)的 Iterator 接口,都需要自己在Symbol.iterator屬性上面部署,這樣才會被for...of循環遍歷。

對象(Object)之所以沒有默認部署 Iterator 接口,是因為對象的哪個屬性先遍歷,哪個屬性后遍歷是不確定的,需要開發者手動指定。本質上,遍歷器是一種線性處理,對于任何非線性的數據結構,部署遍歷器接口,就等于部署一種線性轉換。不過,嚴格地說,對象部署遍歷器接口并不是很必要,因為這時對象實際上被當作 Map 結構使用,ES5 沒有 Map 結構,而 ES6 原生提供了。

一個對象如果要具備可被for...of循環調用的 Iterator 接口,就必須在Symbol.iterator的屬性上部署遍歷器生成方法(原型鏈上的對象具有該方法也可)。

 1 class RangeIterator {
 2   constructor(start, stop) {
 3     this.value = start;
 4     this.stop = stop;
 5   }
 6 
 7   [Symbol.iterator]() { return this; }
 8 
 9   next() {
10     var value = this.value;
11     if (value < this.stop) {
12       this.value++;
13       return {done: false, value: value};
14     }
15     return {done: true, value: undefined};
16   }
17 }
18 
19 function range(start, stop) {
20   return new RangeIterator(start, stop);
21 }
22 
23 for (var value of range(0, 3)) {
24   console.log(value); // 0, 1, 2
25 }

上面代碼是一個類部署 Iterator 接口的寫法。Symbol.iterator屬性對應一個函數,執行后返回當前對象的遍歷器對象。

下面是通過遍歷器實現指針結構的例子。

 1 function Obj(value) {
 2   this.value = value;
 3   this.next = null;
 4 }
 5 
 6 Obj.prototype[Symbol.iterator] = function() {
 7   var iterator = { next: next };
 8 
 9   var current = this;
10 
11   function next() {
12     if (current) {
13       var value = current.value;
14       current = current.next;
15       return { done: false, value: value };
16     } else {
17       return { done: true };
18     }
19   }
20   return iterator;
21 }
22 
23 var one = new Obj(1);
24 var two = new Obj(2);
25 var three = new Obj(3);
26 
27 one.next = two;
28 two.next = three;
29 
30 for (var i of one){
31   console.log(i); // 1, 2, 3
32 }

上面代碼首先在構造函數的原型鏈上部署Symbol.iterator方法,調用該方法會返回遍歷器對象iterator,調用該對象的next方法,在返回一個值的同時,自動將內部指針移到下一個實例。

下面是另一個為對象添加 Iterator 接口的例子。

 1 let obj = {
 2   data: [ 'hello', 'world' ],
 3   [Symbol.iterator]() {
 4     const self = this;
 5     let index = 0;
 6     return {
 7       next() {
 8         if (index < self.data.length) {
 9           return {
10             value: self.data[index++],
11             done: false
12           };
13         } else {
14           return { value: undefined, done: true };
15         }
16       }
17     };
18   }
19 };

對于類似數組的對象(存在數值鍵名和length屬性),部署 Iterator 接口,有一個簡便方法,就是Symbol.iterator方法直接引用數組的 Iterator 接口。

1 NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
2 // 或者
3 NodeList.prototype[Symbol.iterator] = [][Symbol.iterator];
4 
5 [...document.querySelectorAll('div')] // 可以執行了

NodeList 對象是類似數組的對象,本來就具有遍歷接口,可以直接遍歷。上面代碼中,我們將它的遍歷接口改成數組的Symbol.iterator屬性,可以看到沒有任何影響。

下面是另一個類似數組的對象調用數組的Symbol.iterator方法的例子。

 1 let iterable = {
 2   0: 'a',
 3   1: 'b',
 4   2: 'c',
 5   length: 3,
 6   [Symbol.iterator]: Array.prototype[Symbol.iterator]
 7 };
 8 for (let item of iterable) {
 9   console.log(item); // 'a', 'b', 'c'
10 }

注意,普通對象部署數組的Symbol.iterator方法,并無效果

 1 let iterable = {
 2   a: 'a',
 3   b: 'b',
 4   c: 'c',
 5   length: 3,
 6   [Symbol.iterator]: Array.prototype[Symbol.iterator]
 7 };
 8 for (let item of iterable) {
 9   console.log(item); // undefined, undefined, undefined
10 }

如果Symbol.iterator方法對應的不是遍歷器生成函數(即會返回一個遍歷器對象),解釋引擎將會報錯。

1 var obj = {};
2 
3 obj[Symbol.iterator] = () => 1;
4 
5 [...obj] // TypeError: [] is not a function

上面代碼中,變量objSymbol.iterator方法對應的不是遍歷器生成函數,因此報錯。

有了遍歷器接口,數據結構就可以用for...of循環遍歷(詳見下文),也可以使用while循環遍歷。

1 var $iterator = ITERABLE[Symbol.iterator]();
2 var $result = $iterator.next();
3 while (!$result.done) {
4   var x = $result.value;
5   // ...
6   $result = $iterator.next();
7 }

上面代碼中,ITERABLE代表某種可遍歷的數據結構,$iterator是它的遍歷器對象。遍歷器對象每次移動指針(next方法),都檢查一下返回值的done屬性,如果遍歷還沒結束,就移動遍歷器對象的指針到下一步(next方法),不斷循環。

?調用Iterator接口的場合

有一些場合會默認調用 Iterator 接口(即Symbol.iterator方法),除了下文會介紹的for...of循環,還有幾個別的場合。

(1)解構賦值

對數組和 Set 結構進行解構賦值時,會默認調用Symbol.iterator方法。

1 let set = new Set().add('a').add('b').add('c');
2 
3 let [x,y] = set;
4 // x='a'; y='b'
5 
6 let [first, ...rest] = set;
7 // first='a'; rest=['b','c'];

(2)擴展運算符

擴展運算符(...)也會調用默認的 Iterator 接口。

1 var str = 'hello';
2 [...str] //  ['h','e','l','l','o']
3 
4 // 例二
5 let arr = ['b', 'c'];
6 ['a', ...arr, 'd']
7 // ['a', 'b', 'c', 'd']

上面代碼的擴展運算符內部就調用 Iterator 接口。

實際上,這提供了一種簡便機制,可以將任何部署了 Iterator 接口的數據結構,轉為數組。也就是說,只要某個數據結構部署了 Iterator 接口,就可以對它使用擴展運算符,將其轉為數組。

1 let arr = [...iterable];

(3)yield*

yield*后面跟的是一個可遍歷的結構,它會調用該結構的遍歷器接口。

 1 let generator = function* () {
 2   yield 1;
 3   yield* [2,3,4];
 4   yield 5;
 5 };
 6 
 7 var iterator = generator();
 8 
 9 iterator.next() // { value: 1, done: false }
10 iterator.next() // { value: 2, done: false }
11 iterator.next() // { value: 3, done: false }
12 iterator.next() // { value: 4, done: false }
13 iterator.next() // { value: 5, done: false }
14 iterator.next() // { value: undefined, done: true }

(4)其他場合

由于數組的遍歷會調用遍歷器接口,所以任何接受數組作為參數的場合,其實都調用了遍歷器接口。下面是一些例子。

  • for...of
  • Array.from()
  • Map(), Set(), WeakMap(), WeakSet()(比如new Map([['a',1],['b',2]])
  • Promise.all()
  • Promise.race()

字符串的 Iterator 接口

字符串是一個類似數組的對象,也原生具有 Iterator 接口。

1 var someString = "hi";
2 typeof someString[Symbol.iterator]
3 // "function"
4 
5 var iterator = someString[Symbol.iterator]();
6 
7 iterator.next()  // { value: "h", done: false }
8 iterator.next()  // { value: "i", done: false }
9 iterator.next()  // { value: undefined, done: true }

上面代碼中,調用Symbol.iterator方法返回一個遍歷器對象,在這個遍歷器上可以調用 next 方法,實現對于字符串的遍歷。

可以覆蓋原生的Symbol.iterator方法,達到修改遍歷器行為的目的。

 1 var str = new String("hi");
 2 
 3 [...str] // ["h", "i"]
 4 
 5 str[Symbol.iterator] = function() {
 6   return {
 7     next: function() {
 8       if (this._first) {
 9         this._first = false;
10         return { value: "bye", done: false };
11       } else {
12         return { done: true };
13       }
14     },
15     _first: true
16   };
17 };
18 
19 [...str] // ["bye"]
20 str // "hi"

上面代碼中,字符串 str 的Symbol.iterator方法被修改了,所以擴展運算符(...)返回的值變成了bye,而字符串本身還是hi

Iterator 接口與 Generator 函數?

 1 let myIterable = {
 2   [Symbol.iterator]: function* () {
 3     yield 1;
 4     yield 2;
 5     yield 3;
 6   }
 7 }
 8 [...myIterable] // [1, 2, 3]
 9 
10 // 或者采用下面的簡潔寫法
11 
12 let obj = {
13   * [Symbol.iterator]() {
14     yield 'hello';
15     yield 'world';
16   }
17 };
18 
19 for (let x of obj) {
20   console.log(x);
21 }
22 // "hello"
23 // "world"

上面代碼中,Symbol.iterator方法幾乎不用部署任何代碼,只要用 yield 命令給出每一步的返回值即可。

遍歷器對象的 return(),throw()?

遍歷器對象除了具有next方法,還可以具有return方法和throw方法。如果你自己寫遍歷器對象生成函數,那么next方法是必須部署的,return方法和throw方法是否部署是可選的。

return方法的使用場合是,如果for...of循環提前退出(通常是因為出錯,或者有break語句),就會調用return方法。如果一個對象在完成遍歷前,需要清理或釋放資源,就可以部署return方法。

 1 function readLinesSync(file) {
 2   return {
 3     [Symbol.iterator]() {
 4       return {
 5         next() {
 6           return { done: false };
 7         },
 8         return() {
 9           file.close();
10           return { done: true };
11         }
12       };
13     },
14   };
15 }

上面代碼中,函數readLinesSync接受一個文件對象作為參數,返回一個遍歷器對象,其中除了next方法,還部署了return方法。下面的兩種情況,都會觸發執行return方法。

 1 // 情況一
 2 for (let line of readLinesSync(fileName)) {
 3   console.log(line);
 4   break;
 5 }
 6 
 7 // 情況二
 8 for (let line of readLinesSync(fileName)) {
 9   console.log(line);
10   throw new Error();
11 }

上面代碼中,情況一輸出文件的第一行以后,就會執行return方法,關閉這個文件;情況二會在執行return方法關閉文件之后,再拋出錯誤。

注意,return方法必須返回一個對象,這是 Generator 規格決定的。

throw方法主要是配合 Generator 函數使用,一般的遍歷器對象用不到這個方法。請參閱《Generator 函數》一章。

for...of 循環

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循環本質上就是調用這個接口產生的遍歷器,可以用下面的代碼證明。

 1 const arr = ['red', 'green', 'blue'];
 2 
 3 for(let v of arr) {
 4   console.log(v); // red green blue
 5 }
 6 
 7 const obj = {};
 8 obj[Symbol.iterator] = arr[Symbol.iterator].bind(arr);
 9 
10 for(let v of obj) {
11   console.log(v); // red green blue
12 }

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

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

1 const arr = ['red', 'green', 'blue'];
2 
3 arr.forEach(function (element, index) {
4   console.log(element); // red green blue
5   console.log(index);   // 0 1 2
6 });

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

1 var arr = ['a', 'b', 'c', 'd'];
2 
3 for (let a in arr) {
4   console.log(a); // 0 1 2 3
5 }
6 
7 for (let a of arr) {
8   console.log(a); // a b c d
9 }

上面代碼表明,for...in循環讀取鍵名,for...of循環讀取鍵值。如果要通過for...of循環,獲取數組的索引,可以借助數組實例的entries方法和keys方法(參見《數組的擴展》一章)。

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

 1 let arr = [3, 5, 7];
 2 arr.foo = 'hello';
 3 
 4 for (let i in arr) {
 5   console.log(i); // "0", "1", "2", "foo"
 6 }
 7 
 8 for (let i of arr) {
 9   console.log(i); //  "3", "5", "7"
10 }

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

Set 和 Map 結構

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

 1 var engines = new Set(["Gecko", "Trident", "Webkit", "Webkit"]);
 2 for (var e of engines) {
 3   console.log(e);
 4 }
 5 // Gecko
 6 // Trident
 7 // Webkit
 8 
 9 var es6 = new Map();
10 es6.set("edition", 6);
11 es6.set("committee", "TC39");
12 es6.set("standard", "ECMA-262");
13 for (var [name, value] of es6) {
14   console.log(name + ": " + value);
15 }
16 // edition: 6
17 // committee: TC39
18 // standard: ECMA-262

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

 1 let map = new Map().set('a', 1).set('b', 2);
 2 for (let pair of map) {
 3   console.log(pair);
 4 }
 5 // ['a', 1]
 6 // ['b', 2]
 7 
 8 for (let [key, value] of map) {
 9   console.log(key + ' : ' + value);
10 }
11 // a : 1
12 // b : 2

計算生成的數據結構

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

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

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

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

類似數組的對象

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

 1 // 字符串
 2 let str = "hello";
 3 
 4 for (let s of str) {
 5   console.log(s); // h e l l o
 6 }
 7 
 8 // DOM NodeList對象
 9 let paras = document.querySelectorAll("p");
10 
11 for (let p of paras) {
12   p.classList.add("test");
13 }
14 
15 // arguments對象
16 function printArgs() {
17   for (let x of arguments) {
18     console.log(x);
19   }
20 }
21 printArgs('a', 'b');
22 // 'a'
23 // 'b'

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

1 for (let x of 'a\uD83D\uDC0A') {
2   console.log(x);
3 }
4 // 'a'
5 // '\uD83D\uDC0A'

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

 1 let arrayLike = { length: 2, 0: 'a', 1: 'b' };
 2 
 3 // 報錯
 4 for (let x of arrayLike) {
 5   console.log(x);
 6 }
 7 
 8 // 正確
 9 for (let x of Array.from(arrayLike)) {
10   console.log(x);
11 }

對象

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

 1 let es6 = {
 2   edition: 6,
 3   committee: "TC39",
 4   standard: "ECMA-262"
 5 };
 6 
 7 for (let e in es6) {
 8   console.log(e);
 9 }
10 // edition
11 // committee
12 // standard
13 
14 for (let e of es6) {
15   console.log(e);
16 }
17 // TypeError: es6[Symbol.iterator] is not a function

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

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

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

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

 1 function* entries(obj) {
 2   for (let key of Object.keys(obj)) {
 3     yield [key, obj[key]];
 4   }
 5 }
 6 
 7 for (let [key, value] of entries(obj)) {
 8   console.log(key, '->', value);
 9 }
10 // a -> 1
11 // b -> 2
12 // c -> 3

與其他遍歷語法的比較

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

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

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

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

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

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

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

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

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

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

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

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

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

1 for (var n of fibonacci) {
2   if (n > 1000)
3     break;
4   console.log(n);
5 }

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

?

轉載于:https://www.cnblogs.com/wanghao123/p/9390592.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/397315.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/397315.shtml
英文地址,請注明出處:http://en.pswp.cn/news/397315.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

MyBatis 特殊字符處理

http://blog.csdn.net/zheng0518/article/details/10449549

計算機操作系統實驗銀行家算法,實驗六 銀行家算法(下)

實驗六 銀行家算法(下)一、實驗說明實驗說明&#xff1a;本次實驗主要是對銀行家算法進行進一步的實踐學習&#xff0c;掌握銀行家算法的整體流程&#xff0c;理解程序測試時每一步的當前狀態&#xff0c;能對當前的資源分配進行預判斷。二、實驗要求1、獲取源代碼2、看懂大致框…

什么原因導致芯片短路_華為為什么突然大量用起了聯發科芯片,或是這三個產品策略原因...

經常關注數碼圈的都知道&#xff0c;近幾年來&#xff0c;隨著華為自研能力的提升&#xff0c;華為幾乎很少采購第三方芯片&#xff0c;近幾年來的絕大多數華為手機&#xff0c;幾乎都是用的自研芯片麒麟系列。并沒有像其它國產品牌那樣用聯發科或者高通的芯片。不過今年卻大不…

如何運行vue項目(維護他人的項目)

假如你是個小白&#xff0c;在公司接手他人的項目&#xff0c;這個時候&#xff0c;該怎么將這個項目跑通&#xff1f; 前提&#xff1a; 首先&#xff0c;這個教程主要針對vue小白&#xff0c;并且不知道安裝node.js環境的。言歸正傳&#xff0c;下面開始教程&#xff1a;在維…

進程操作

2019獨角獸企業重金招聘Python工程師標準>>> 一、創建一個進程 進程是系統中最基本的執行單位。Linux系統允許任何一個用戶進程創建一個子進程&#xff0c;創建之后&#xff0c;子進程存在于系統之中并獨立于父進程。 關于父進程與子進程這兩個概念&#xff0c;除了…

計算機硬件發展的特點有哪些,簡述計算機的發展歷程及各代計算機的特點。

滿意答案Karen0491推薦于 2017.11.25采納率&#xff1a;40% 等級&#xff1a;6已幫助&#xff1a;608人世界上第一臺計算機是1946年問世的&#xff0c;根據計算機的性能和軟硬件技術&#xff0c;將計算機發展劃分成以下幾個階段&#xff1a;①第一階段&#xff1a;電子管計算…

電餅鍋的樣式圖片價格_進口琺瑯鑄鐵鍋專場,精致小廚娘們來康康!

兩個月前&#xff0c;小灰兔我寫了《10個高顏值居家好物&#xff0c;讓你在朋友圈萬眾矚目&#xff01;》一文&#xff0c;曾有小伙伴私信說這張圖簡直就是夢想中廚房的亞子強烈同意&#xff01;&#xff01;&#xff01;有多少女孩子&#xff0c;看到顏值炒雞高的鍋路都走不動…

在UITouch事件中畫圓圈-iOS8 Swift基礎教程

這篇教程主要內容展示如何利用Core Graphics Framework畫圓圈,當用戶點擊屏幕時隨機生成不同大小的圓,這篇教程在Xcode6和iOS8下編譯通過。 打開Xcode,新建項目選擇Single View Application,Product Name填寫iOS8SwiftDrawingCirclesTutorial,Organization Name和Organization …

瀏覽器兼容性問題

轉載于:https://www.cnblogs.com/python-machine/p/9406084.html

sql server 2005 (select查詢語句用法)

select * from userInfo where age like 2[25]功能&#xff1a;查詢userInfo表中age字段&#xff0c;所有以2開頭&#xff0c;且第二位是2或5的記錄。select * from userInfo where name like _娜_功能&#xff1a;查詢userInfo表中name&#xff08;char(6)&#xff09;字段所有…

有人在遠程使用計算機是什么意思,如何遠程控制計算機,計算機遠程控制有什么用途...

對于每個人來說&#xff0c;計算機都是至關重要的家用電器. 因為使用計算機可以使我們的業余生活豐富多彩. 隨著Internet的普及&#xff0c;越來越多的用戶開始學習自己使用計算機. 但是&#xff0c;操作中仍然存在很多問題&#xff0c;只要每個人都學會了遠程控制&#xff0c;…

圖學java基礎篇之IO

java io體系 如圖可以看出&#xff0c;java的io按照包來劃分的話可以分為三大塊&#xff1a;io、nio、aio&#xff0c;但是從使用角度來看&#xff0c;這三塊其實揉雜在一起的&#xff0c;下邊我們先來概述下這三塊&#xff1a; io:主要包含字符流和字節流&#xff0c;我們常用…

boot界面上下鍵調節鍵不能動_為什么電腦一開機就自動進入BIOS界面

電腦故障的問題表現形式很多&#xff0c;比如說為什么電腦藍屏&#xff0c;為什么電腦一開機就自動進入BIOS界面等。這些問題往往另很多網友不知所措。今天小編就針對電腦一開機就自動進入BIOS界面的問題&#xff0c;教下大家具體的解決方法。1、你的BIOS電池沒有電了。解決方法…

ArcEngine數據刪除幾種方法和性能比較

轉自原文 ArcEngine數據刪除幾種方法和性能比較 一、 幾種刪除方法代碼 1. 查詢結果中刪除 private void Delete1(IFeatureClass PFeatureclass) { IQueryFilter pQueryFilter new QueryFilterClass(); pQueryFilter.WhereClause "objectID<" DeleteNum; IFe…

計算機組成原理中英對照篇,信息科學系課程介紹(中英對照).doc

文檔介紹&#xff1a;信息科學系課程介紹(中英對照)序號:1課程編碼:14001010課程名稱:計算機基礎學分:1周學時:2開課系部:信息科學系預修課程:無修讀對象:信息管理與信息系統專業本科生課程簡介:主要介紹計算機發展歷史、常用操作系統、辦公和其它應用軟件、等內容。通過本課程…

句子相似度--余弦相似度算法的實現

1、余弦相似度余弦距離&#xff0c;也稱為余弦相似度&#xff0c;是用向量空間中兩個向量夾角的余弦值作為衡量兩個個體間差異的大小的度量。余弦值越接近1&#xff0c;就表明夾角越接近0度&#xff0c;也就是兩個向量越相似&#xff0c;這就叫"余弦相似性"。 上圖兩…

python之模塊calendar(匯集了日歷相關的操作)

# -*- coding: utf-8 -*- #python 27 #xiaodeng #calendar日歷模塊import calendar#3個大類&#xff1a; calendar.Calendar(firstweekday0) calendar.TextCalendar(firstweekday0) calendar.HTMLCalendar(firstweekday0)#返回某月日歷 cal calendar.month(2011, 11) print ca…

紅帽436——HA高可用集群之概念篇

一、集群概念&#xff1a;集群&#xff1a;提高性能&#xff0c;降低成本&#xff0c;提高可擴展性&#xff0c;增強可靠性&#xff0c;任務調度室集群中的核心技術。集群作用:保證業務不斷 集群三種網絡&#xff1a;業務網絡,集群網絡,存儲網絡 二、集群三種類型&#xff1a;…

計算機網絡的構成教學反思,《計算機網絡知識》教學反思.doc

文檔介紹&#xff1a;《計算機網絡知識》教學反思本課時以理論性內容為主,在日常教學過程中往往會以課本宣讀為主,很難引起學生的學****興趣。然而在教學中若能夠合理的采用多種教學方式,做到理論聯系實際,將達到良好的教學效果。我在本節課教學中,以我校的校園網為模型,充分利…

按季度分類匯總_2019年純堿行業相關上市公司季報 與半年報情況匯總

2019年純堿行業相關上市公司季報與半年報情況匯總經百川盈孚統計&#xff0c;目前純堿行業相關上市公司共計11家(包含三家ST股)&#xff0c;已經全部公布2019年一季度報告。2019年上半年度報告僅五家公司公布&#xff0c;其中包含&#xff1a;大連大化、山東海化、華昌化工、湖…