鍍金池/ 教程/ HTML/ Iterator 和 for...of 循環(huán)
數(shù)組的擴(kuò)展
Class和Module
Set 和 Map 數(shù)據(jù)結(jié)構(gòu)
異步操作
對象的擴(kuò)展
Generator 函數(shù)
數(shù)值的擴(kuò)展
變量的解構(gòu)賦值
Iterator 和 for...of 循環(huán)
Promise 對象
參考鏈接
ECMAScript 6簡介
作者簡介
字符串的擴(kuò)展
編程風(fēng)格
let 和 const 命令
函數(shù)的擴(kuò)展

Iterator 和 for...of 循環(huán)

Iterator(遍歷器)

概念

JavaScript 原有的數(shù)據(jù)結(jié)構(gòu),主要是數(shù)組(Array)和對象(Object),ES6 又添加了 Map 和 Set,用戶還可以組合使用它們,定義自己的數(shù)據(jù)結(jié)構(gòu)。這就需要一種統(tǒng)一的接口機(jī)制,來處理所有不同的數(shù)據(jù)結(jié)構(gòu)。

遍歷器(Iterator)就是這樣一種機(jī)制。它屬于一種接口規(guī)格,任何數(shù)據(jù)結(jié)構(gòu)只要部署這個(gè)接口,就可以完成遍歷操作,即依次處理該結(jié)構(gòu)的所有成員。它的作用有兩個(gè),一是為各種數(shù)據(jù)結(jié)構(gòu),提供一個(gè)統(tǒng)一的、簡便的接口,二是使得數(shù)據(jù)結(jié)構(gòu)的成員能夠按某種次序排列。在 ES6 中,遍歷操作特指 for...of 循環(huán),即 Iterator 接口主要供 for...of 消費(fèi)。

遍歷器的遍歷過程是這樣的:它提供了一個(gè)指針,默認(rèn)指向當(dāng)前數(shù)據(jù)結(jié)構(gòu)的起始位置。也就是說,遍歷器返回一個(gè)內(nèi)部指針。第一次調(diào)用遍歷器的 next 方法,可以將指針指向到第一個(gè)成員,第二次調(diào)用 next 方法,就指向第二個(gè)成員,直至指向數(shù)據(jù)結(jié)構(gòu)的結(jié)束位置。每一次調(diào)用,都會(huì)返回當(dāng)前成員的信息,具體來說,就是返回一個(gè)包含 value 和 done 兩個(gè)屬性的對象。其中,value 屬性是當(dāng)前成員的值,done 屬性是一個(gè)布爾值,表示遍歷是否結(jié)束。

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


function makeIterator(array){
  var nextIndex = 0;
  return {
    next: function(){
      return nextIndex < array.length ?
        {value: array[nextIndex++], done: false} :
        {value: undefined, done: true};
    }
  }
}

var it = makeIterator(['a', 'b']);

it.next() // { value: "a", done: false }
it.next() // { value: "b", done: false }
it.next() // { value: undefined, done: true }

上面代碼定義了一個(gè) makeIterator 函數(shù),它的作用就是返回?cái)?shù)組的遍歷器。對數(shù)組['a', 'b']執(zhí)行這個(gè)函數(shù),就會(huì)返回該數(shù)組的遍歷器 it。

遍歷器 it 的 next 方法,用來移動(dòng)指針。開始時(shí),指針指向數(shù)組的開始位置。然后,每次調(diào)用 next 方法,指針就會(huì)指向數(shù)組的下一個(gè)成員。第一次調(diào)用,指向 a;第二次調(diào)用,指向 b。

next 方法返回一個(gè)對象,表示當(dāng)前位置的信息。這個(gè)對象具有 value 和 done 兩個(gè)屬性,value 屬性返回當(dāng)前位置的成員,done 屬性是一個(gè)布爾值,表示遍歷是否結(jié)束,即是否還有必要再一次調(diào)用 next 方法。

總之,遍歷器是一個(gè)對象,具有 next 方法。調(diào)用 next 方法,就可以遍歷事先給定的數(shù)據(jù)結(jié)構(gòu)。

由于遍歷器只是把接口規(guī)格加到數(shù)據(jù)結(jié)構(gòu)之上,所以,遍歷器與它所遍歷的那個(gè)數(shù)據(jù)結(jié)構(gòu),實(shí)際上是分開的,完全可以寫出沒有對應(yīng)數(shù)據(jù)結(jié)構(gòu)的遍歷器,或者說用遍歷器模擬出數(shù)據(jù)結(jié)構(gòu)。下面是一個(gè)無限運(yùn)行的遍歷器例子。


function idMaker(){
  var index = 0;

  return {
    next: function(){
      return {value: index++, done: false};
    }
  }
}

var it = idMaker();

it.next().value // '0'
it.next().value // '1'
it.next().value // '2'
// ...

上面的例子中,idMaker 函數(shù)返回的對象就是遍歷器,但是并沒有對應(yīng)的數(shù)據(jù)結(jié)構(gòu),或者說遍歷器自己描述了一個(gè)數(shù)據(jù)結(jié)構(gòu)出來。

在 ES6 中,有些數(shù)據(jù)結(jié)構(gòu)原生提供遍歷器(比如數(shù)組),即不用任何處理,就可以被 for...of 循環(huán)遍歷,有些就不行(比如對象)。原因在于,這些數(shù)據(jù)結(jié)構(gòu)部署了 System.iterator 屬性(詳見下文)。凡是部署了 System.iterator 屬性的數(shù)據(jù)結(jié)構(gòu),就稱為部署了遍歷器接口。調(diào)用這個(gè)接口,就會(huì)返回一個(gè)遍歷器。

如果使用 TypeScript 的寫法,遍歷器接口、遍歷器和 next 方法返回值的規(guī)格可以描述如下。


interface Iterable {
  [System.iterator]() : Iterator,
}

interface Iterator {
  next(value?: any) : IterationResult,
}

interface IterationResult {
  value: any,
  done: boolean,
}

默認(rèn)的 Iterator 接口

Iterator 接口的目的,就是為所有數(shù)據(jù)結(jié)構(gòu),提供了一種統(tǒng)一的訪問機(jī)制,即 for...of 循環(huán)(詳見下文)。當(dāng)使用 for...of 循環(huán)遍歷某種數(shù)據(jù)結(jié)構(gòu)時(shí),該循環(huán)會(huì)自動(dòng)去尋找 Iterator 接口。

ES6 規(guī)定,默認(rèn)的 Iterator 接口部署在數(shù)據(jù)結(jié)構(gòu)的 Symbol.iterator 屬性,或者一個(gè)數(shù)據(jù)結(jié)構(gòu)只要具有 Symbol.iterator 屬性,就可以認(rèn)為是“可遍歷的”(iterable)。也就是說,調(diào)用 Symbol.iterator 方法,就會(huì)得到當(dāng)前數(shù)據(jù)結(jié)構(gòu)的默認(rèn)遍歷器。Symbol.iterator 本身是一個(gè)表達(dá)式,返回 Symbol 對象的 iterator 屬性,這是一個(gè)預(yù)定義好的、類型為 Symbol 的特殊值,所以要放在方括號(hào)內(nèi)(請參考 Symbol 一節(jié))。

在 ES6 中,有三類數(shù)據(jù)結(jié)構(gòu)原生具備 Iterator 接口:數(shù)組、某些類似數(shù)組的對象、Set 和 Map 結(jié)構(gòu)。


let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator]();

iter.next() // { value: 'a', done: false }
iter.next() // { value: 'b', done: false }
iter.next() // { value: 'c', done: false }
iter.next() // { value: undefined, done: true }

上面代碼中,變量 arr 是一個(gè)數(shù)組,原生就具有遍歷器接口,部署在 arr 的 Symbol.iterator 屬性上面。所以,調(diào)用這個(gè)屬性,就得到遍歷器。

上面提到,原生就部署 iterator 接口的數(shù)據(jù)結(jié)構(gòu)有三類,對于這三類數(shù)據(jù)結(jié)構(gòu),不用自己寫遍歷器,for...of 循環(huán)會(huì)自動(dòng)遍歷它們。除此之外,其他數(shù)據(jù)結(jié)構(gòu)(主要是對象)的 Iterator 接口,都需要自己在Symbol.iterator屬性上面部署,這樣才會(huì)被 for...of 循環(huán)遍歷。

對象(Object)之所以沒有默認(rèn)部署 Iterator 接口,是因?yàn)閷ο蟮哪膫€(gè)屬性先遍歷,哪個(gè)屬性后遍歷是不確定的,需要開發(fā)者手動(dòng)指定。本質(zhì)上,遍歷器是一種線性處理,對于任何非線性的數(shù)據(jù)結(jié)構(gòu),部署遍歷器接口,就等于部署一種線性轉(zhuǎn)換。不過,嚴(yán)格地說,對象部署遍歷器接口并不是很必要,因?yàn)檫@時(shí)對象實(shí)際上被當(dāng)作 Map 結(jié)構(gòu)使用,ES5 沒有 Map 結(jié)構(gòu),而 ES6 原生提供了。

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


class RangeIterator {
  constructor(start, stop) {
    this.value = start;
    this.stop = stop;
  }

  [Symbol.iterator]() { return this; }

  next() {
    var value = this.value;
    if (value < this.stop) {
      this.value++;
      return {done: false, value: value};
    } else {
      return {done: true, value: undefined};
    }
  }
}

function range(start, stop) {
  return new RangeIterator(start, stop);
}

for (var value of range(0, 3)) {
  console.log(value);
}

上面代碼是一個(gè)類部署 Iterator 接口的寫法。Symbol.iterator 屬性對應(yīng)一個(gè)函數(shù),執(zhí)行后返回當(dāng)前對象的遍歷器。

下面是通過遍歷器實(shí)現(xiàn)指針結(jié)構(gòu)的例子。


function Obj(value){
  this.value = value;
  this.next = null;
}

Obj.prototype[Symbol.iterator] = function(){

  var iterator = {
    next: next
  };

  var current = this;

  function next(){
    if (current){
      var value = current.value;
      var done = current == null;
      current = current.next;
      return {
        done: done,
        value: value
      }
    } else {
      return {
        done: true
      }
    }
  }
  return iterator;
}

var one = new Obj(1);
var two = new Obj(2);
var three = new Obj(3);

one.next = two;
two.next = three;

for (var i of one){
  console.log(i)
}
// 1
// 2
// 3

上面代碼首先在構(gòu)造函數(shù)的原型鏈上部署 Symbol.iterator 方法,調(diào)用該方法會(huì)返回遍歷器對象 iterator,調(diào)用該對象的 next 方法,在返回一個(gè)值的同時(shí),自動(dòng)將內(nèi)部指針移到下一個(gè)實(shí)例。

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


let obj = {
  data: [ 'hello', 'world' ],
  [Symbol.iterator]() {
    const self = this;
    let index = 0;
    return {
      next() {
        if (index < self.data.length) {
          return {
            value: self.data[index++],
            done: false
          };
        } else {
          return { value: undefined, done: true };
        }
      }
    };
  }
};

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


NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];

如果 Symbol.iterator 方法返回的不是遍歷器,解釋引擎將會(huì)報(bào)錯(cuò)。


var obj = {};

obj[Symbol.iterator] = () => 1;

[...obj] // TypeError: [] is not a function

上面代碼中,變量 obj 的 Symbol.iterator 方法返回的不是遍歷器,因此報(bào)錯(cuò)。

有了遍歷器接口,數(shù)據(jù)結(jié)構(gòu)就可以用 for...of 循環(huán)遍歷(詳見下文),也可以使用 while 循環(huán)遍歷。

var $iterator = ITERABLE[Symbol.iterator]();
var $result = $iterator.next();
while (!$result.done) {
  var x = $result.value;
  // ...
  $result = $iterator.next();
}

上面代碼中,ITERABLE 代表某種可遍歷的數(shù)據(jù)結(jié)構(gòu),$iterator 是它的遍歷器。遍歷器每次移動(dòng)指針(next 方法),都檢查一下返回值的 done 屬性,如果遍歷還沒結(jié)束,就移動(dòng)遍歷器的指針到下一步(next 方法),不斷循環(huán)。

調(diào)用默認(rèn) iterator 接口的場合

有一些場合會(huì)默認(rèn)調(diào)用 iterator 接口(即 Symbol.iterator 方法),除了下文會(huì)介紹的 for...of 循環(huán),還有幾個(gè)別的場合。

(1)解構(gòu)賦值

對數(shù)組和 Set 結(jié)構(gòu)進(jìn)行解構(gòu)賦值時(shí),會(huì)默認(rèn)調(diào)用 iterator 接口。


let set = new Set().add('a').add('b').add('c');

let [x,y] = set;
// x='a'; y='b'

let [first, ...rest] = set;
// first='a'; rest=['b','c'];

(2)擴(kuò)展運(yùn)算符

擴(kuò)展運(yùn)算符(...)也會(huì)調(diào)用默認(rèn)的 iterator 接口。


// 例一
var str = 'hello';
[...str] //  ['h','e','l','l','o']

// 例二
let arr = ['b', 'c'];
['a', ...arr, 'd']
// ['a', 'b', 'c', 'd']

上面代碼的擴(kuò)展運(yùn)算符內(nèi)部就調(diào)用 iterator 接口。

實(shí)際上,這提供了一種簡便機(jī)制,可以將任何部署了 iterator 接口的數(shù)據(jù)結(jié)構(gòu),轉(zhuǎn)為數(shù)組。也就是說,只要某個(gè)數(shù)據(jù)結(jié)構(gòu)部署了 iterator 接口,就可以對它使用擴(kuò)展運(yùn)算符,將其轉(zhuǎn)為數(shù)組。


let arr = [...iterable];

(3)其他場合

以下場合也會(huì)用到默認(rèn)的 iterator 接口,可以查閱相關(guān)章節(jié)。

  • yield*
  • Array.from()
  • Map(), Set(), WeakMap(), WeakSet()
  • Promise.all(), Promise.race()

原生具備 iterator 接口的數(shù)據(jù)結(jié)構(gòu)

《數(shù)組的擴(kuò)展》一章中提到,ES6 對數(shù)組提供 entries()、keys() 和 values() 三個(gè)方法,就是返回三個(gè)遍歷器。


var arr = [1, 5, 7];
var arrEntries = arr.entries();

arrEntries.toString()
// "[object Array Iterator]"

arrEntries === arrEntries[Symbol.iterator]()
// true

上面代碼中,entries 方法返回的是一個(gè)遍歷器(iterator),本質(zhì)上就是調(diào)用了Symbol.iterator方法。

字符串是一個(gè)類似數(shù)組的對象,也原生具有 Iterator 接口。


var someString = "hi";
typeof someString[Symbol.iterator]
// "function"

var iterator = someString[Symbol.iterator]();

iterator.next()  // { value: "h", done: false }
iterator.next()  // { value: "i", done: false }
iterator.next()  // { value: undefined, done: true }

上面代碼中,調(diào)用Symbol.iterator方法返回一個(gè)遍歷器,在這個(gè)遍歷器上可以調(diào)用 next 方法,實(shí)現(xiàn)對于字符串的遍歷。

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


var str = new String("hi");

[...str] // ["h", "i"]

str[Symbol.iterator] = function() {
  return {
    next: function() {
      if (this._first) {
        this._first = false;
        return { value: "bye", done: false };
      } else {
        return { done: true };
      }
    },
    _first: true
  };
};

[...str] // ["bye"]
str // "hi"

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

Iterator 接口與 Generator 函數(shù)

部署Symbol.iterator方法的最簡單實(shí)現(xiàn),還是使用下一節(jié)要提到的 Generator 函數(shù)。


var myIterable = {};

myIterable[Symbol.iterator] = function* () {
  yield 1;
  yield 2;
  yield 3;
};
[...myIterable] // [1, 2, 3]

// 或者采用下面的簡潔寫法

let obj = {
  * [Symbol.iterator]() {
    yield 'hello';
    yield 'world';
  }
};

for (let x of obj) {
  console.log(x);
}
// hello
// world

return(),throw()

遍歷器除了具有 next 方法(必備),還可以具有 return 方法和 throw 方法(可選)。

for...of 循環(huán)如果提前退出(通常是因?yàn)槌鲥e(cuò),或者有 break 語句或 continue 語句),就會(huì)調(diào)用 return 方法。如果一個(gè)對象在完成遍歷前,需要清理或釋放資源,就可以部署 return 方法。

throw 方法主要是配合 Generator 函數(shù)使用,一般的遍歷器用不到這個(gè)方法。請參閱《Generator函數(shù)》的章節(jié)。

for...of 循環(huán)

ES6 借鑒 C++、Java、C# 和 Python 語言,引入了 for...of 循環(huán),作為遍歷所有數(shù)據(jù)結(jié)構(gòu)的統(tǒng)一的方法。一個(gè)數(shù)據(jù)結(jié)構(gòu)只要部署了Symbol.iterator方法,就被視為具有 iterator 接口,就可以用 for...of 循環(huán)遍歷它的成員。也就是說,for...of 循環(huán)內(nèi)部調(diào)用的是數(shù)據(jù)結(jié)構(gòu)的Symbol.iterator方法。

for...of 循環(huán)可以使用的范圍包括數(shù)組、Set 和 Map 結(jié)構(gòu)、某些類似數(shù)組的對象(比如 arguments 對象、DOM NodeList 對象)、后文的 Generator 對象,以及字符串。

數(shù)組

數(shù)組原生具備 iterator 接口,for...of 循環(huán)本質(zhì)上就是調(diào)用這個(gè)接口產(chǎn)生的遍歷器,可以用下面的代碼證明。


const arr = ['red', 'green', 'blue'];
let iterator  = arr[Symbol.iterator]();

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

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

上面代碼的 for...of 循環(huán)的兩種寫法是等價(jià)的。

for...of 循環(huán)可以代替數(shù)組實(shí)例的 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 循環(huán),只能獲得對象的鍵名,不能直接獲取鍵值。ES6 提供 for...of 循環(huán),允許遍歷獲得鍵值。


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

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

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

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

Set 和 Map 結(jié)構(gòu)

Set 和 Map 結(jié)構(gòu)也原生具有 Iterator 接口,可以直接使用 for...of 循環(huán)。


var engines = 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 結(jié)構(gòu)和 Map 結(jié)構(gòu)。值得注意的地方有兩個(gè),首先,遍歷的順序是按照各個(gè)成員被添加進(jìn)數(shù)據(jù)結(jié)構(gòu)的順序。其次,Set 結(jié)構(gòu)遍歷時(shí),返回的是一個(gè)值,而 Map 結(jié)構(gòu)遍歷時(shí),返回的是一個(gè)數(shù)組,該數(shù)組的兩個(gè)成員分別為當(dāng)前 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

計(jì)算生成的數(shù)據(jù)結(jié)構(gòu)

有些數(shù)據(jù)結(jié)構(gòu)是在現(xiàn)有數(shù)據(jù)結(jié)構(gòu)的基礎(chǔ)上,計(jì)算生成的。比如,ES6 的數(shù)組、Set、Map 都部署了以下三個(gè)方法,調(diào)用后都返回遍歷器。

  • entries() 返回一個(gè)遍歷器,用來遍歷 [鍵名, 鍵值] 組成的數(shù)組。對于數(shù)組,鍵名就是索引值;對于 Set,鍵名與鍵值相同。Map 結(jié)構(gòu)的 iterator 接口,默認(rèn)就是調(diào)用 entries 方法。
  • keys() 返回一個(gè)遍歷器,用來遍歷所有的鍵名。
  • values() 返回一個(gè)遍歷器,用來遍歷所有的鍵值。

這三個(gè)方法調(diào)用后生成的遍歷器,所遍歷的都是計(jì)算生成的數(shù)據(jù)結(jié)構(gòu)。


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

類似數(shù)組的對象

類似數(shù)組的對象包括好幾類。下面是 for...of 循環(huán)用于字符串、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 循環(huán)還有一個(gè)特點(diǎn),就是會(huì)正確識(shí)別 32 位 UTF-16 字符。


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

并不是所有類似數(shù)組的對象都具有 iterator 接口,一個(gè)簡便的解決方法,就是使用 Array.from 方法將其轉(zhuǎn)為數(shù)組。


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

// 報(bào)錯(cuò)
for (let x of arrayLike) {
  console.log(x);
}

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

對象

對于普通的對象,for...of 結(jié)構(gòu)不能直接使用,會(huì)報(bào)錯(cuò),必須部署了 iterator 接口后才能使用。但是,這樣情況下,for...in 循環(huán)依然可以用來遍歷鍵名。


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

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

for (e of es6) {
  console.log(e);
}
// TypeError: es6 is not iterable

上面代碼表示,對于普通的對象,for...in 循環(huán)可以遍歷鍵名,for...of 循環(huán)會(huì)報(bào)錯(cuò)。

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

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

在對象上部署 iterator 接口的代碼,參見本章前面部分。一個(gè)方便的方法是將數(shù)組的Symbol.iterator屬性,直接賦值給其他對象的Symbol.iterator屬性。比如,想要讓 for...of 循環(huán)遍歷 jQuery 對象,只要加上下面這一行就可以了。

jQuery.prototype[Symbol.iterator] =
  Array.prototype[Symbol.iterator];

另一個(gè)方法是使用 Generator 函數(shù)將對象重新包裝一下。

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

與其他遍歷語法的比較

以數(shù)組為例,JavaScript 提供多種遍歷語法。最原始的寫法就是 for 循環(huán)。

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

這種寫法比較麻煩,因此數(shù)組提供內(nèi)置的 forEach 方法。

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

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

for...in 循環(huán)可以遍歷數(shù)組的鍵名。

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

for...in 循環(huán)有幾個(gè)缺點(diǎn)。

1)數(shù)組的鍵名是數(shù)字,但是 for...in 循環(huán)是以字符串作為鍵名“0”、“1”、“2”等等。

2)for...in 循環(huán)不僅遍歷數(shù)字鍵名,還會(huì)遍歷手動(dòng)添加的其他鍵,甚至包括原型鏈上的鍵。

3)某些情況下,for...in 循環(huán)會(huì)以任意順序遍歷鍵名。

總之,for...in 循環(huán)主要是為遍歷對象而設(shè)計(jì)的,不適用于遍歷數(shù)組。

for...of 循環(huán)相比上面幾種做法,有一些顯著的優(yōu)點(diǎn)。

for (let value of myArray) {
  console.log(value);
}
  • 有著同 for...in 一樣的簡潔語法,但是沒有 for...in 那些缺點(diǎn)。
  • 不同用于 forEach 方法,它可以與 break、continue 和 return 配合使用。
  • 提供了遍歷所有數(shù)據(jù)結(jié)構(gòu)的統(tǒng)一操作接口。