面試:你知道爲何會有 Generator 嗎

提示: 本文是 github 上《Understanding ECMAScript 6》 的筆記整理,代碼示例也來源於此。你們有時間能夠直接讀這本書。雖是英文,但通俗易懂,很是推薦。node

以前面試時有被問到爲何會有 Generator, 還好沒懵逼。想知道的嗎,往下翻。git

原文連接es6

這裏主要介紹 generator 的由來和一些基本概念,已經瞭解了或者想了解 generator 高級用法的能夠看這篇 怎麼往 Generator 裏拋個錯?github

循環的問題

ES5 裏遍歷一個數組須要這樣:面試

const colors = ["red", "green", "blue"];
for (var i = 0, len = colors.length; i < len; i++) {
  console.log(colors[i]);
}
複製代碼

能夠看出:chrome

  1. 它既要追蹤下標位置,
  2. 還要判斷循環什麼時候中止。

這段代碼邏輯簡單, 但寫法複雜而枯燥。並且很經常使用,因此很容易因手抖而出bug。爲了簡化寫法,下降出錯機率,ES6 引入了一些新的語法,其中一個是 iterator數組

什麼是 iterator

iterator 也是一種對象,不過它有着專爲迭代而設計的接口。它有next 方法,該方法返回一個包含 valuedone 兩個屬性的對象 (下稱 result )。前者是迭代的值,後者是代表迭代是否完成的標誌 -- 布爾值: true 表示迭代完成,false 表示沒有。iterator 內部有指向迭代位置的指針,每次調用next, 自動移動指針並返回相應的 resultbash

下面是一個自定義的 iterator:函數

function createIterator(items) {
  var i = 0;
  return {
    next: function () {
      var done = (i >= items.length);
      var value = !done ? items[i++] : undefined; return {
        done: done,
        value: value
      };
    }
  };
}



var iterator = createIterator([1, 2, 3]);
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next());  // "{ value: 2, done: false }"
console.log(iterator.next());  // "{ value: 3, done: false }"
console.log(iterator.next());  // "{ value: undefined, done: true }"
// 後續的全部調用返回的結果都同樣
console.log(iterator.next());  // "{ value: undefined, done: true }"
複製代碼

能夠看出,只須要咱們不斷執行next就能夠,不須要手動追蹤迭代的位置。比開篇的循環簡單了些。post

注意: 最後一次迭代返回的value, 並非集合(如上例子中的 items )裏的值,而是 undedined 或者函數返回值。 查看這裏,能夠了解返回值與result的關係

generator 是什麼

上面雖然是比 for 循環簡單了些,但手動寫個 iterator 太麻煩了,因此ES6 推出 generator ,方便建立 iterator。也就是說,generator 就是一個返回值爲 iterator 的函數。

其語法以下:

function* createIterator() {
  yield 1;
  yield 2;
  yield 3;
}
// generators能夠像正常函數同樣被調用,不一樣的是會返回一個 iterator
let iterator = createIterator();
console.log(iterator.next().value); // 1
console.log(iterator.next().value); // 2
console.log(iterator.next().value); // 3
複製代碼

例子很明瞭,* 標明這是個 generatorsyield 用來在調用 next時返回 value

方法裏的 generator, 能夠簡寫:

let o = {
  createIterator: function* (items) {
    for (let i = 0; i < items.length; i++) {
      yield items[i];
    }
  }
};


// 等同於
let o = {
  *createIterator(items) {
    for (let i = 0; i < items.length; i++) {
      yield items[i];
    }
  }
};

複製代碼

能夠看到,建立 generator 函數有三種方法

  1. 函數聲明

    function* createIterator() {...}
    複製代碼
  2. 函數表達式

    const createIterator = function* () {...}
    複製代碼
  3. 對象裏的簡寫方式

    let o = {
      *createIterator(items) {
         ...
       }
    };
    複製代碼

能夠認爲,就是在函數關鍵字 function 和函數名之間加 *, 只不過,不一樣場景下關鍵和函數名能夠省略罷了

[function] * [name]() {}

// * 能夠靠近關鍵字也能夠靠近函數名,或兩不靠近,均可以
複製代碼

注意點:

  1. 須要注意的是,yield 不能跨函數:

    function* createIterator(items) {
      items.forEach(function (item) {
        // 語法錯誤
        yield item + 1;
      });
    }
    複製代碼
  2. 箭頭函數不能用作 generator

iterable 和 for-of 循環

iterable

iterator 緊密是相關的,有個叫 iterable 的對象。它有個 Symbol.iterator 屬性,其值是個 generator 函數。

用代碼描述的話,iterable 長這樣:

let collection = {
  items: [],
  *[Symbol.iterator]() {
    for (let item of this.items) {
      yield item;
    }
  }
};
複製代碼

ES6 裏的集合如數組、set、map甚至字符串,都是 iterablegenerator 建立的 iterator 都被默認添加了 Symbol.iterator ,因此,它們也是 iterable

全部的 iterable, 也均可以使用 for-of 循環。

for-of 循環

雖然有了 iterator ,只要調用它的next方法,就能夠迭代了。可是,每次手動調用也太麻煩了,因此 ES6 推出了 for-of 循環:

const colors = ["red", "green", "blue"];
for (let color of colors) {
  console.log(color);
}
複製代碼

對比開篇的 for 循環:

const colors = ["red", "green", "blue"];
for (var i = 0, len = colors.length; i < len; i++) {
  console.log(colors[i]);
}
複製代碼

能夠看出,for-of 循環

  1. 不用追蹤迭代位置;
  2. 不用判斷循環終止條件;

只須要聲明變量活動每次迭代的值便可,簡潔明瞭。

注意:for-of 只能用在 iterable 上,用其餘對象上會報錯。

內置的 iterator

iteratorES6 裏一個重要的部分,一些內置的數據類型都內置了 iterator,方便開發。

這裏簡要介紹下有 iterator 的數據類型。雖然簡單,但不是不重要。

集合的 iterator

ES6 裏的集合有三類:

  1. 數組
  2. set
  3. map

他們下面的 iterator 有:

entries(): 返回迭代結果爲鍵值對的 iterator; values(): 返回迭代結果爲集合裏的值的 iterator; keys(): 返回迭代結果爲集合裏的 keyiterator;

下面以 map 爲例:

let tracking = new Map([
  ['name', 'jeyvie'],
  ['pro', 'fd'],
  ['hobby', 'programming']
]);

for (let entry of tracking.entries()) {
  console.log(entry);
}
// ["name", "jeyvie"]
// ["pro", "fd"]
// ["hobby", "programming"]

for (let key of tracking.keys()) {
  console.log(key);
}
// name
// pro
// hobby

for (let value of tracking.values()) {
  console.log(value);
}
// jeyvie
// fd
// programming
複製代碼

其中 set 裏的 keyvalue 相等,都是 set 裏的值。數組的 key 是其下標 index, value 是裏面的值。

另外,各集合都有默認的 iteratorfor-of 調用。 其執行結果,反應的是集合如何被初始化的。

如上例子 tracking

for (let value of tracking) {
  console.log(value);
}
// ["name", "jeyvie"]
// ["pro", "fd"]
// ["hobby", "programming"]
複製代碼

對應這 Map 實例化是出入的子項 -- 有兩個元素的數組。

其餘的set、 數組與之相似,不贅述了。

須要留意,經驗證 chrome v65node v8.0 裏數組都沒有 values()。也許是由於對數組而言,它比較冗餘吧。

字符串的 iterator

字符串裏 iterator,理解一句話就能夠: 是基於 code point 而不是code unit 迭代的。比較一下兩個例子:

例子1: 基於 code unit

var message = "A 𠮷 B";
for (let i=0; i < message.length; i++) {
    console.log(message[i]);
}

// A
// (空)
// � 
// � 
// (空)
// B
複製代碼

例子2: 基於 code point

var message = "A 𠮷 B";
for (let c of message) {
  console.log(c);
}

// A
// (空)
// 𠮷 
// (空)
// B
複製代碼

基於code point能夠理解爲基於字符, 關於它是什麼,能夠查看字符串那章。

NodeList 的 iterator

之前要迭代 NodeList (元素集合),須要用 for 循環:

// 能夠這樣
for (var i=0, len=NodeList; i<len; i++) {
	var el = NodeList[i]
	// ....
}

複製代碼

ES6 能夠直接這樣:

for (let el of NodeList) {
	// el 就是集合裏的每一個元素
}
複製代碼

另外,如今 NodeList 也有forEach方法了

spread操做符

展開操做符 ... 和 iterable

展開操做符能夠用在全部的 iterable 上,並根據該 iterable 的默認 iterator 決定取哪一個值。

好比咱們能夠將字符串轉爲數組:

[...'A𠮷B']
// ['A', '𠮷', 'B']
複製代碼

結語

這裏以 for 循環的問題開始, 講述 ES6 裏怎麼解決這個問題,由此引出 generator, 而後帶出了 iteratoriterable, 以及操做 iterablefor-of。 最後講了 ES6 裏結合 for-of, 能使內置集合的操做起來更加方便。

固然了,for 循環問題也許並非最初 generator 產生的緣由,generator 也不僅是解決了 for 循環這一個問題,它還有更多的高級功能,在實際中的做用更大,後面我會繼續發表出來。

那我爲何要寫這篇文章,還成了個「標題黨」呢?由於我好奇,我不僅是想知道一項技術當前到底怎麼用,仍是知道它爲什麼出現,想知道其前因後果。這樣,一方面能更瞭解了技術,另外一反面,也更能瞭解技術發展的趨勢,這樣,才能可能看得清前方啊,不至於東奔西撞,走那麼多彎路。

最後,一切都是拋磚引玉,歡迎你們批評指正!

另外,generator 高級用法篇 怎麼往 Generator 裏拋個錯? 已經寫完,歡迎方家指正。

相關文章
相關標籤/搜索