根據ES6制訂的標準自定義迭代器實現起來比較復雜,因此ES6又引入了生成器的概念,生成器(Generator)是一個能直接創建并返回迭代器的特殊函數,可將其賦給可迭代對象的Symbol.iterator屬性。與普通函數不同,生成器不僅可以暫停函數內部的執行(即維護內部的狀態),在聲明時還需要包含一個星號(\*),并且擁有next()、return()和throw()三個迭代器方法。
## 一、function\*
  生成器在聲明時,需要把星號加到function關鍵字與函數名之間,但ES6沒有規定星號兩邊是否需要空格,因此下面四種寫法都是允許的,本篇將采用第一種寫法。
~~~
function* generator() {}
function*generator() {}
function *generator() {}
function * generator() {}
~~~
  生成器也能通過函數表達式創建,如下代碼所示。注意,不能用箭頭函數創建生成器。
~~~
var iterator = function* () {};
~~~
  生成器雖然不能作為構造函數使用,但可以是對象的一個方法,并且還支持[第5篇](https://www.cnblogs.com/strick/p/10173583.html)提到的簡潔方式的寫法,如下所示。
~~~
var obj = {
*generator() {}
};
~~~
## 二、yield
  生成器之所以能在其內部實現分批執行,還要多虧ES6新增的yield關鍵字。這個關鍵字可標記暫停位置,具體使用可參考下面的代碼。
~~~
function* generator() {
var count = 0;
while (count < 2)
yield count++;
return count;
}
var iterator = generator();
~~~
  雖然生成器的調用方式和普通函數相同,但它不會馬上返回函數的結果(即不能立刻執行)。而是先返回一個它所生成的迭代器,然后再調用其next()方法恢復內部語句的執行(如下代碼所示),直至遇到yield關鍵字,再次暫停,如此反復,一直持續到函數的末尾或碰到return語句才終止這套循環操作。
~~~
iterator.next(); //{value: 0, done: false}
iterator.next(); //{value: 1, done: false}
iterator.next(); //{value: 2, done: true}
~~~
**1)yield表達式**
  yield關鍵字的后面可以跟一個表達式,例如代碼中的count++。生成器的next()方法能夠返回一個IteratorResult對象,其done屬性用于判斷生成器是否執行完畢,即是否還有yield表達式。關于IteratorResult兩個屬性的值,需要分情況說明,具體如下所列。
  (1)當生成器還在執行時,value的值可通過計算yield表達式得到,done的值為false。
  (2)當生成器執行完畢時,value的值是undefined,done的值為true。
  (3)當遇到return語句時,value的值就是return后面跟的值,done的值為true。
  要想遍歷生成器,除了借助next()方法之外,還可以使用for-of循環。但要注意,遍歷到的是yield表達式的計算結果,如下所示。
~~~
/********************
0
1
********************/
for(var step of iterator) {
console.log(step);
}
~~~
**2)優先級和結合性**
  因為yield可以單獨使用(例如x=yield),所以它并不是一個運算符。雖然如此,但它還是包含優先級和結合性的概念。yield的優先級很低,僅比擴展運算符和逗號高,如果要提前計算,可以像下面這樣用一對圓括號包裹。
~~~
1 + (yield 2);
~~~
  yield的結合性與等號一樣,也是從右往左,例如yield yield 1相當于yield(yield 1)。另外,yield有一個很重要的限制,就是它只能存在于生成器內,在其它位置出現都會有異常,包括生成器中的子函數內,如下所示。
~~~
function* error() {
function inner() {
yield 1;
}
}
~~~
## 三、3個方法
**1)next()**
  本節開篇的時候曾提到過生成器包含三個迭代器方法,接下來將圍繞這三個方法展開講解。首先介紹的是next()方法,它能接收一個參數,而這個參數會成為上一個yield表達式的返回值。以下面的代碼為例,calculate()函數包含兩個yield表達式,在創建生成器后,調用了兩次next()方法,第一次沒有傳參,第二次傳入的數字10被賦給了x變量。
~~~
function* calculate() {
let x = yield 1;
let y = yield x + 2;
return y;
}
var iterator = calculate();
iterator.next(); //{value: 1, done: false}
iterator.next(10); //{value: 12, done: false}
~~~
  注意,第一次調用next()方法時,即使傳進了參數,這個參數也會被忽略,因為此時還不存在上一個yield表達式。
**2)return()**
  接下來介紹的是return()方法,它能提前終止當前生成器,類似于在函數體內馬上執行return語句。下面沿用上一個示例,將函數名改成stop,第二次調用的方法改成return()。
~~~
function* stop() {
let x = yield 1;
let y = yield x + 2;
return y;
}
var iterator = stop();
iterator.next(); //{value: 1, done: false}
iterator.return(10); //{value: 10, done: true}
iterator.next(); //{value: undefined, done: true}
~~~
  return()方法也能接收一個參數,而從上面的調用結果中可以得知,這個參數相當于return運算符后面跟的值,如下所示。
~~~
function* stop() {
let x = yield 1;
return 10;
}
~~~
**3)throw()**
  最后介紹的是throw()方法,它能強制生成器拋出一個錯誤。此方法也有一個參數,但這個參數只能被try-catch語句中的catch部分接收。下面用一個例子演示throw()方法的具體使用。
~~~
function* especial() {
var count = 1;
try {
yield count;
} catch (e) {
count = 2;
console.log(e); //"inner"
}
yield count + 3;
}
var iterator = especial();
iterator.next(); //{value: 1, done: false}
try {
iterator.throw("inner"); //{value: 5, done: false}
iterator.next(); //{value: undefined, done: true}
iterator.throw("outer");
} catch (e) {
console.log(e); //"outer"
}
~~~
  在especial生成器的內部和外部各有一條try-catch語句。第一次調用throw()方法,在生成器內部先捕獲拋出的錯誤,再把傳入的字符串“inner”賦給catch的e參數,接著執行yield count + 3,最后返回一個計算過的IteratorResult對象。第二次調用throw()方法,由于生成器已執行完畢,因此只能在外部將錯誤捕獲。
## 四、yield\*
  在yield關鍵字后面跟一個星號(兩邊的空格可選),就能將執行權委托給另一個生成器或可迭代對象。以下面代碼為例,在delegation生成器中,有兩個yield\*表達式,第一個跟的是數組,第二個跟的是generator生成器(相當于將兩個生成器合并)。
~~~
function* generator() {
var count = 0;
while (count < 2)
yield count++;
return count;
}
function* delegation() {
yield* ["a", "b"];
var result = yield* generator();
console.log(result); //2
}
var iterator = delegation();
iterator.next(); //{value: "a", done: false}
iterator.next(); //{value: "b", done: false}
iterator.next(); //{value: 0, done: false}
iterator.next(); //{value: 1, done: false}
iterator.next(); //{value: undefined, done: true}
~~~
  從上面的遍歷結果中可知,delegation生成器先訪問數組的每個元素,再計算generator生成器中的yield表達式,并將其返回值賦給了result變量。
## 五、異步編程
  在ES6之前,要實現異步編程,最常用的方法是用回調函數,例如捕獲Ajax通信中的響應內容,如下所示。
~~~
function fetch(callback) {
$.getJSON("server.php", {}, function(json) {
callback.call(this, json);
});
}
function asyn() {
fetch(function(json) {
console.log(json); //{code: 200, msg: "操作成功"}
});
}
asyn();
~~~
  fetch()函數調用了jQuery中能發起Ajax請求的getJSON()方法,在其載入成功時的回調函數內間接調用了callback參數(即傳遞進來的回調函數),其參數就是響應內容。
  接下來將asyn()變為生成器,并在其內部添加yield表達式,然后在getJSON()的回調函數中調用生成器的next()方法,并將響應內容作為參數傳入。
~~~
function fetch() {
$.getJSON("server.php", {}, function(json) {
gen.next(json);
});
}
function* asyn() {
var result = yield fetch();
console.log(result); //{code: 200, msg: "操作成功"}
}
var gen = asyn();
gen.next();
~~~
  通過上面的代碼可知,生成器能用同步的方式實現異步編程,從而有效避免了層層嵌套的回調金字塔。
*****
> 原文出處:
[博客園-ES6躬行記](https://www.cnblogs.com/strick/category/1372951.html)
[知乎專欄-ES6躬行記](https://zhuanlan.zhihu.com/pwes6)
已建立一個微信前端交流群,如要進群,請先加微信號freedom20180706或掃描下面的二維碼,請求中需注明“看云加群”,在通過請求后就會把你拉進來。還搜集整理了一套[面試資料](https://github.com/pwstrick/daily),歡迎瀏覽。

推薦一款前端監控腳本:[shin-monitor](https://github.com/pwstrick/shin-monitor),不僅能監控前端的錯誤、通信、打印等行為,還能計算各類性能參數,包括 FMP、LCP、FP 等。
- ES6
- 1、let和const
- 2、擴展運算符和剩余參數
- 3、解構
- 4、模板字面量
- 5、對象字面量的擴展
- 6、Symbol
- 7、代碼模塊化
- 8、數字
- 9、字符串
- 10、正則表達式
- 11、對象
- 12、數組
- 13、類型化數組
- 14、函數
- 15、箭頭函數和尾調用優化
- 16、Set
- 17、Map
- 18、迭代器
- 19、生成器
- 20、類
- 21、類的繼承
- 22、Promise
- 23、Promise的靜態方法和應用
- 24、代理和反射
- HTML
- 1、SVG
- 2、WebRTC基礎實踐
- 3、WebRTC視頻通話
- 4、Web音視頻基礎
- CSS進階
- 1、CSS基礎拾遺
- 2、偽類和偽元素
- 3、CSS屬性拾遺
- 4、浮動形狀
- 5、漸變
- 6、濾鏡
- 7、合成
- 8、裁剪和遮罩
- 9、網格布局
- 10、CSS方法論
- 11、管理后臺響應式改造
- React
- 1、函數式編程
- 2、JSX
- 3、組件
- 4、生命周期
- 5、React和DOM
- 6、事件
- 7、表單
- 8、樣式
- 9、組件通信
- 10、高階組件
- 11、Redux基礎
- 12、Redux中間件
- 13、React Router
- 14、測試框架
- 15、React Hooks
- 16、React源碼分析
- 利器
- 1、npm
- 2、Babel
- 3、webpack基礎
- 4、webpack進階
- 5、Git
- 6、Fiddler
- 7、自制腳手架
- 8、VSCode插件研發
- 9、WebView中的頁面調試方法
- Vue.js
- 1、數據綁定
- 2、指令
- 3、樣式和表單
- 4、組件
- 5、組件通信
- 6、內容分發
- 7、渲染函數和JSX
- 8、Vue Router
- 9、Vuex
- TypeScript
- 1、數據類型
- 2、接口
- 3、類
- 4、泛型
- 5、類型兼容性
- 6、高級類型
- 7、命名空間
- 8、裝飾器
- Node.js
- 1、Buffer、流和EventEmitter
- 2、文件系統和網絡
- 3、命令行工具
- 4、自建前端監控系統
- 5、定時任務的調試
- 6、自制短鏈系統
- 7、定時任務的進化史
- 8、通用接口
- 9、微前端實踐
- 10、接口日志查詢
- 11、E2E測試
- 12、BFF
- 13、MySQL歸檔
- 14、壓力測試
- 15、活動規則引擎
- 16、活動配置化
- 17、UmiJS版本升級
- 18、半吊子的可視化搭建系統
- 19、KOA源碼分析(上)
- 20、KOA源碼分析(下)
- 21、花10分鐘入門Node.js
- 22、Node環境升級日志
- 23、Worker threads
- 24、低代碼
- 25、Web自動化測試
- 26、接口攔截和頁面回放實驗
- 27、接口管理
- 28、Cypress自動化測試實踐
- 29、基于Electron的開播助手
- Node.js精進
- 1、模塊化
- 2、異步編程
- 3、流
- 4、事件觸發器
- 5、HTTP
- 6、文件
- 7、日志
- 8、錯誤處理
- 9、性能監控(上)
- 10、性能監控(下)
- 11、Socket.IO
- 12、ElasticSearch
- 監控系統
- 1、SDK
- 2、存儲和分析
- 3、性能監控
- 4、內存泄漏
- 5、小程序
- 6、較長的白屏時間
- 7、頁面奔潰
- 8、shin-monitor源碼分析
- 前端性能精進
- 1、優化方法論之測量
- 2、優化方法論之分析
- 3、瀏覽器之圖像
- 4、瀏覽器之呈現
- 5、瀏覽器之JavaScript
- 6、網絡
- 7、構建
- 前端體驗優化
- 1、概述
- 2、基建
- 3、后端
- 4、數據
- 5、后臺
- Web優化
- 1、CSS優化
- 2、JavaScript優化
- 3、圖像和網絡
- 4、用戶體驗和工具
- 5、網站優化
- 6、優化閉環實踐
- 數據結構與算法
- 1、鏈表
- 2、棧、隊列、散列表和位運算
- 3、二叉樹
- 4、二分查找
- 5、回溯算法
- 6、貪心算法
- 7、分治算法
- 8、動態規劃
- 程序員之路
- 大學
- 2011年
- 2012年
- 2013年
- 2014年
- 項目反思
- 前端基礎學習分享
- 2015年
- 再一次項目反思
- 然并卵
- PC網站CSS分享
- 2016年
- 制造自己的榫卯
- PrimusUI
- 2017年
- 工匠精神
- 2018年
- 2019年
- 前端學習之路分享
- 2020年
- 2021年
- 2022年
- 2023年
- 2024年
- 日志
- 2020