《深入理解ES6》筆記——迭代器(Iterator)和生成器(Generator)(8)

二月發表於2017-07-26

迭代器(Iterator)

ES5實現迭代器

迭代器是什麼?遇到這種新的概念,莫慌張。

迭代器是一種特殊物件,每一個迭代器物件都有一個next(),該方法返回一個物件,包括value和done屬性。

ES5實現迭代器的程式碼如下:

//實現一個返回迭代器物件的函式,注意該函式不是迭代器,返回結果才叫做迭代器。
function createIterator(items) {
  var i = 0;
  return {
    next() {
      var done = (i >= items.length); // 判斷i是否小於遍歷的物件長度。
      var value = !done ? items[i++] : undefined; //如果done為false,設定value為當前遍歷的值。
      return {
        done,
        value
      }
    }
  }
}
const a = createIterator([1, 2, 3]);

//該方法返回的最終是一個物件,包含value、done屬性。
console.log(a.next()); //{value: 1, done: false}
console.log(a.next()); //{value: 2, done: false}
console.log(a.next()); //{value: 3, done: false}
console.log(a.next()); //{value: undefined, done: true}

生成器(Generator)

生成器是函式:用來返回迭代器。

這個概念有2個關鍵點,一個是函式、一個是返回迭代器。這個函式不是上面ES5中建立迭代器的函式,而是ES6中特有的,一個帶有*(星號)的函式,同時你也需要使用到yield。

//生成器函式,ES6內部實現了迭代器功能,你要做的只是使用yield來迭代輸出。
function *createIterator() {
  yield 1;
  yield 2;
  yield 3;
}
const a = createIterator();
console.log(a.next()); //{value: 1, done: false}
console.log(a.next()); //{value: 2, done: false}
console.log(a.next()); //{value: 3, done: false}
console.log(a.next()); //{value: undefined, done: true}

生成器的yield關鍵字有個神奇的功能,就是當你執行一次next(),那麼只會執行一個yield後面的內容,然後語句終止執行。

在for迴圈中使用迭代器

即使你是在for迴圈中使用yield關鍵字,也會暫停迴圈。

function *createIterator(items) {
  for(let i = 0; i < items.length;  i++) {
    yield items[i]
  }
}
const a = createIterator([1, 2, 3]);
console.log(a.next()); //{value: 1, done: false}

yield使用限制

yield只可以在生成器函式內部使用,如果在非生成器函式內部使用,則會報錯。

function *createIterator(items) {
    //你應該在這裡使用yield
  items.map((value, key) => {
    yield value //語法錯誤,在map的回撥函式裡面使用了yield
  })
}
const a = createIterator([1, 2, 3]);
console.log(a.next()); //無輸出

生成器函式表示式

函式表示式很簡單,就是下面這種寫法,也叫匿名函式,不用糾結。

const createIterator = function *() {
    yield 1;
    yield 2;
}
const a = createIterator();
console.log(a.next());

在物件中新增生成器函式

一個物件長這樣:

const obj = {}

我們可以在obj中新增一個生成器,也就是新增一個帶星號的方法:

const obj = {
  a: 1,
  *createIterator() {
    yield this.a
  }
}
const a = obj.createIterator();
console.log(a.next());  //{value: 1, done: false}

可迭代物件和for of迴圈

再次默讀一遍,迭代器是物件,生成器是返回迭代器的函式。

凡是通過生成器生成的迭代器,都是可以迭代的物件(可迭代物件具有Symbol.iterator屬性),也就是可以通過for of將value遍歷出來。

function *createIterator() {
  yield 1;
  yield 2;
  yield 3;
}
const a = createIterator();
for(let value of a) {
  console.log(value)
}
// 1 2 3

上面的例子告訴我們生成器函式返回的迭代器是一個可以迭代的物件。其實我們這裡要研究的是Symbol.iterator的用法。

function *createIterator() {
  yield 1;
  yield 2;
  yield 3;
}
const a = createIterator(); //a是一個迭代器
const s = a[Symbol.iterator]();//使用Symbol.iterator訪問迭代器
console.log(s.next()) //{value: 1, done: false}

Symbol.iterator還可以用來檢測一個物件是否可迭代:

typeof obj[Symbol.iterator] === "function"

建立可迭代物件

在ES6中,陣列、Set、Map、字串都是可迭代物件。

預設情況下定義的物件(object)是不可迭代的,但是可以通過Symbol.iterator建立迭代器。

const obj = {
  items: []
}
obj.items.push(1);//這樣子雖然向陣列新增了新元素,但是obj不可迭代
for (let x of obj) {
  console.log(x) // _iterator[Symbol.iterator] is not a function
}

//接下來給obj新增一個生成器,使obj成為一個可以迭代的物件。
const obj = {
  items: [],
  *[Symbol.iterator]() {
    for (let item of this.items) {
      yield item;
    }
  }
}
obj.items.push(1)
//現在可以通過for of迭代obj了。
for (let x of obj) {
  console.log(x)
}

內建迭代器

上面提到了,陣列、Set、Map都是可迭代物件,即它們內部實現了迭代器,並且提供了3種迭代器函式呼叫。

1、entries() 返回迭代器:返回鍵值對

//陣列
const arr = [`a`, `b`, `c`];
for(let v of arr.entries()) {
  console.log(v)
}
// [0, `a`] [1, `b`] [2, `c`]

//Set
const arr = new Set([`a`, `b`, `c`]);
for(let v of arr.entries()) {
  console.log(v)
}
// [`a`, `a`] [`b`, `b`] [`c`, `c`]

//Map
const arr = new Map();
arr.set(`a`, `a`);
arr.set(`b`, `b`);
for(let v of arr.entries()) {
  console.log(v)
}
// [`a`, `a`] [`b`, `b`]

2、values() 返回迭代器:返回鍵值對的value

//陣列
const arr = [`a`, `b`, `c`];
for(let v of arr.values()) {
  console.log(v)
}
//`a` `b` `c`

//Set
const arr = new Set([`a`, `b`, `c`]);
for(let v of arr.values()) {
  console.log(v)
}
// `a` `b` `c`

//Map
const arr = new Map();
arr.set(`a`, `a`);
arr.set(`b`, `b`);
for(let v of arr.values()) {
  console.log(v)
}
// `a` `b`

3、keys() 返回迭代器:返回鍵值對的key

//陣列
const arr = [`a`, `b`, `c`];
for(let v of arr.keys()) {
  console.log(v)
}
// 0 1 2

//Set
const arr = new Set([`a`, `b`, `c`]);
for(let v of arr.keys()) {
  console.log(v)
}
// `a` `b` `c`

//Map
const arr = new Map();
arr.set(`a`, `a`);
arr.set(`b`, `b`);
for(let v of arr.keys()) {
  console.log(v)
}
// `a` `b`

雖然上面列舉了3種內建的迭代器方法,但是不同集合的型別還有自己預設的迭代器,在for of中,陣列和Set的預設迭代器是values(),Map的預設迭代器是entries()。

for of迴圈解構

物件本身不支援迭代,但是我們可以自己新增一個生成器,返回一個key,value的迭代器,然後使用for of迴圈解構key和value。

const obj = {
  a: 1,
  b: 2,
  *[Symbol.iterator]() {
    for(let i in obj) {
      yield [i, obj[i]]
    }
  }
}
for(let [key, value] of obj) {
  console.log(key, value)
}
// `a` 1, `b` 2

字串迭代器

const str = `abc`;
for(let v of str) {
  console.log(v)
}
// `a` `b` `c`

NodeList迭代器

迭代器真是無處不在啊,dom節點的迭代器你應該已經用過了。

const divs = document.getElementByTagName(`div`);
for(let d of divs) {
  console.log(d)
}

展開運算子和迭代器

const a = [1, 2, 3];
const b = [4, 5, 6];
const c = [...a, ...b]
console.log(c) // [1, 2, 3, 4, 5, 6]

高階迭代器功能

你說什麼?上面講了一堆廢話都是基礎功能?還有高階功能沒講?

高階功能不復雜,就是傳參、丟擲異常、生成器返回語句、委託生成器。

1、傳參

生成器裡面有2個yield,當執行第一個next()的時候,返回value為1,然後給第二個next()傳入引數10,傳遞的引數會替代掉上一個next()的yield返回值。在下面的例子中就是first。

function *createIterator() {
  let first = yield 1;
  yield first + 2;
}
let i = createIterator();
console.log(i.next()); // {value: 1, done: false}
console.log(i.next(10)); // {value: 12, done: false}

2、在迭代器中丟擲錯誤

function *createIterator() {
  let first = yield 1;
  yield first + 2;
}
let i = createIterator();
console.log(i.next()); // {value: 1, done: false}
console.log(i.throw(new Error(`error`))); // error
console.log(i.next()); //不再執行

3、生成器返回語句

生成器中新增return表示退出操作。
function *createIterator() {
let first = yield 1;
return;
yield first + 2;
}
let i = createIterator();
console.log(i.next()); // {value: 1, done: false}
console.log(i.next()); // {value: undefined, done: true}

4、委託生成器

生成器巢狀生成器

function *aIterator() {
  yield 1;
}
function *bIterator() {
  yield 2;
}
function *cIterator() {
  yield *aIterator()
  yield *bIterator()
}

let i = cIterator();
console.log(i.next()); // {value: 1, done: false}
console.log(i.next()); // {value: 2, done: false}

非同步任務執行器

ES6之前,我們使用非同步的操作方式是呼叫函式並執行回撥函式。

書上舉的例子挺好的,在nodejs中,有一個讀取檔案的操作,使用的就是回撥函式的方式。

var fs = require("fs");
fs.readFile("xx.json", function(err, contents) {
  //在回撥函式中做一些事情
})

那麼任務執行器是什麼呢?

任務執行器是一個函式,用來迴圈執行生成器,因為我們知道生成器需要執行N次next()方法,才能執行完,所以我們需要一個自動任務執行器幫我們做這些事情,這就是任務執行器的作用。

下面我們編寫一個非同步任務執行器。

//taskDef是一個生成器函式,run是非同步任務執行器
function run(taskDef) {
  let task = taskDef(); //呼叫生成器
  let result = task.next(); //執行生成器的第一個next(),返回result
  function step() {
    if(!result.done) {
    //如果done為false,則繼續執行next(),並且迴圈step,直到done為true退出。
      result = task.next(result.value);
      step();
    }
  }
  step(); //開始執行step()
}

測試一下我們編寫的run方法,我們不再需要console.log N個next了,因為run執行器已經幫我們做了迴圈執行操作:

run(function *() {
  let value = yield 1;
  value = yield value + 20;
  console.log(value) // 21
})

總結

本章講了3個概念,迭代器、生成器、任務執行器。

迭代器是一個物件。

生成器是一個函式,它最終返回迭代器。

任務執行器一個函式(或者也叫生成器的回撥函式),幫我們自動執行生成器的內部運算,最終返回迭代器。

不知道看到這裡,你明白3者的區別和用法沒?

=> 返回文章列表

相關文章