前言
很多同學(xué)在第一次聽到生成器這個(gè)概念的時(shí)候,總覺得是前端高大上的東西垛叨,可能現(xiàn)在依然有很多前端同學(xué)不理解這個(gè)概念额获,今天就從幾個(gè)最常用的場景入手,來解析下生成器的應(yīng)用晶衷。
相關(guān)概念解釋
我們看了很多書和文章蓝纲,都會(huì)說生成器Generator 函數(shù)是一個(gè)狀態(tài)機(jī)阴孟,封裝了多個(gè)內(nèi)部狀態(tài)。Generator 函數(shù)除了狀態(tài)機(jī)税迷,還是一個(gè)遍歷器對(duì)象生成函數(shù)永丝。返回的遍歷器對(duì)象,可以依次遍歷 Generator 函數(shù)內(nèi)部的每一個(gè)狀態(tài)箭养。
看到這個(gè)生成器的定義慕嚷,我也感覺完全懵逼。我們把這個(gè)定義畫重點(diǎn)毕泌。通過一個(gè)個(gè)簡單的例子和概念的解釋闯冷,爭取一字一句的搞懂。
這里先解釋下什么是 遍歷器對(duì)象懈词?
要解釋這個(gè)東西蛇耀,我們先從平時(shí)能理解的簡單概念出發(fā), 我們平時(shí)寫代碼遍歷對(duì)象的時(shí)候坎弯,是不是可以通過 for ...of
來處理纺涤。例如 String
、Array
抠忘、TypedArray
撩炊、Map
和 Set
均可以通過 for ...of
來遍歷。很人多會(huì)認(rèn)為理所當(dāng)然崎脉,有沒有深層的去思考會(huì)這樣呢拧咳? 是什么黑魔法實(shí)現(xiàn)了遍歷呢?其實(shí)有心的朋友囚灼,只要在網(wǎng)上搜索一下骆膝,就可以得到想要的答案。這里的遍歷器對(duì)象其實(shí)是實(shí)現(xiàn)了ES6的定制的一組補(bǔ)充規(guī)范灶体,迭代器協(xié)議阅签。
那么接著我們學(xué)習(xí)下,什么是迭代器協(xié)議蝎抽?我們看下MDN的定義
迭代器協(xié)議定義了產(chǎn)生一系列值(無論是有限個(gè)還是無限個(gè))的標(biāo)準(zhǔn)方式政钟。當(dāng)值為有限個(gè)時(shí),所有的值都被迭代完畢后樟结,則會(huì)返回一個(gè)默認(rèn)返回值养交。
近一步講只有實(shí)現(xiàn)了一個(gè)next()方法, 才能成為一個(gè)迭代器瓢宦, 這個(gè)next 方法返回 擁有2個(gè)屬性的一個(gè)對(duì)象碎连。
- done 表達(dá)這個(gè)迭代器是否將當(dāng)前迭代的序列迭代完成,如果迭代完成刁笙,則返回true破花。否則返回false谦趣。
- value 每次迭代過程中的值,迭代完成了座每,就返回undefined.
這么按照原理講前鹅,太枯燥了,具體的還是去MDN上詳細(xì)學(xué)習(xí)吧峭梳。我們還是從代碼中學(xué)習(xí)的快一些舰绘。結(jié)合代碼去理解原理。
實(shí)現(xiàn)一個(gè)生成迭代器對(duì)象的函數(shù)
function makeIterator(array) {
let nextIndex = 0;
// 返回的對(duì)象是一個(gè)迭代器對(duì)象葱椭。擁有一個(gè)next方法捂寿,next方法執(zhí)行后返回一個(gè){value:, done: } 形式的對(duì)象,這樣就實(shí)現(xiàn)了迭代器協(xié)議孵运。
return {
next: function () {
return nextIndex < array.length ? {
value: array[nextIndex++],
done: false
} : {
done: true
};
}
};
}
let it = makeIterator(['喲', '呀']);
console.log(it.next().value); // '喲'
console.log(it.next().value); // '呀'
console.log(it.next().done); // true
通過上面的簡單解釋秦陋,我們應(yīng)該理解了什么是遍歷器對(duì)象(跟 迭代器對(duì)象是同一個(gè)意思),只有實(shí)現(xiàn)了迭代器協(xié)議的治笨,都是一個(gè)迭代器對(duì)象驳概。 很顯然,生成器實(shí)現(xiàn)了迭代器協(xié)議旷赖。所以生成器執(zhí)行后顺又,返回的是一個(gè)迭代器對(duì)象。我們可以通過next方法來遍歷等孵。
生成器
接下來我們進(jìn)入正題稚照,開始我們的生成器之旅
簡單先解釋下生成器的2個(gè)特征
- function關(guān)鍵字與函數(shù)名之間有一個(gè)星號(hào)
- 函數(shù)體內(nèi)部使用yield表達(dá)式,定義不同的內(nèi)部狀態(tài)
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
// hw 返回的是一個(gè)迭代器俯萌,因?yàn)?helloWorldGenerator實(shí)現(xiàn)了迭代器協(xié)議果录,這里還懵逼的,可以耐心看看上面的解釋
var hw = helloWorldGenerator();
執(zhí)行的結(jié)果是這樣的:
hw.next()
// { value: 'hello', done: false }
hw.next()
// { value: 'world', done: false }
hw.next()
// { value: 'ending', done: true }
hw.next()
// { value: undefined, done: true }
也就是生成器底層實(shí)現(xiàn)了迭代器協(xié)議绳瘟。yield 后面的值雕憔,是每次next函數(shù)返回的value值。
這是我們需要理解的第一點(diǎn)糖声,也是最重要的一點(diǎn)。
接來下了分瘦,我們看另一個(gè)重要的例子:
var myIterable = {};
myIterable[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};
[...myIterable] // [1, 2, 3]
通過以上代碼蘸泻,我們引出另一個(gè)重要的概念, 可迭代協(xié)議
嘲玫。
其實(shí)迭代協(xié)議可以分為2個(gè)協(xié)議: 可迭代協(xié)議和迭代器協(xié)議悦施。迭代器協(xié)議是上面介紹過的。那什么是可迭代協(xié)議呢去团?
我們就不粘貼定義了抡诞,這里直接看看可迭代協(xié)議如果應(yīng)用于一個(gè)對(duì)象穷蛹,是對(duì)象成為可迭代對(duì)象呢?
要成為可迭代對(duì)象昼汗, 一個(gè)對(duì)象必須實(shí)現(xiàn)
**@@iterator**
方法肴熏。這意味著對(duì)象(或者它原型鏈上的某個(gè)對(duì)象)必須有一個(gè)鍵為@@iterator
的屬性,可通過常量Symbol.iterator
訪問該屬性顷窒。當(dāng)一個(gè)對(duì)象需要被迭代的時(shí)候(比如被置入一個(gè)for...of
循環(huán)時(shí))蛙吏,首先,會(huì)不帶參數(shù)調(diào)用它的@@iterator
方法鞋吉,然后使用此方法返回的迭代器獲得要迭代的值鸦做。
我們?nèi)σ幌轮攸c(diǎn)
- 可迭代對(duì)象要有一個(gè)
Symbol.iterator
屬性。 -
Symbol.iterator
的屬性值是一個(gè)方法谓着,返回的是一個(gè)迭代器對(duì)象泼诱。
通過以上概念的講解,我們在回過頭來看?? 上面這段代碼:
Generator 函數(shù)就是迭代器生成函數(shù)赊锚,因此可以把 Generator 賦值給對(duì)象的Symbol.iterator屬性治筒,從而使得該對(duì)象具可遍歷。一個(gè)對(duì)象可遍歷其實(shí)是實(shí)現(xiàn)了Iterator 接口改抡。
一種數(shù)據(jù)結(jié)構(gòu)只要部署了 Iterator 接口矢炼,我們就稱這種數(shù)據(jù)結(jié)構(gòu)是“可遍歷的”(iterable)。
這里我們又引出了新的概念阿纤。Iterator接口句灌,其實(shí)Iterator接口也是我們的老朋友。
ES6 規(guī)定欠拾,默認(rèn)的 Iterator 接口部署在數(shù)據(jù)結(jié)構(gòu)的Symbol.iterator屬性胰锌,或者說,一個(gè)數(shù)據(jù)結(jié)構(gòu)只要具有Symbol.iterator屬性藐窄,就可以認(rèn)為是“可遍歷的”(iterable)
讀到這里的你资昧,是否可以把 Iterator 接口與可迭代對(duì)象和迭代器對(duì)象之前的關(guān)系梳理清楚呢?
我個(gè)人的理解總結(jié)一下荆忍,Iterator 接口 是一個(gè)抽象的概念格带,一個(gè)對(duì)象實(shí)現(xiàn)了Iterator 接口,大白話就是:這個(gè)對(duì)象有一個(gè)Symbol.iterator屬性刹枉,屬性值是一個(gè)函數(shù)叽唱,函數(shù)返回的是一個(gè)迭代器對(duì)象。
這樣是不是把之前支離破碎的知識(shí)給串起來了微宝。Iterator 接口主要供for...of消費(fèi)棺亭。
原生具備 Iterator 接口的數(shù)據(jù)結(jié)構(gòu)如下。
Array
Map
Set
String
TypedArray
- 函數(shù)的 arguments 對(duì)象
- NodeList 對(duì)象
好了蟋软,解釋了這些概念镶摘,我們回歸正題嗽桩,繼續(xù)學(xué)習(xí)生成器的概念??。
function* gen(){
// some code
}
var g = gen();
g[Symbol.iterator]() === g
上面代碼中凄敢,gen是一個(gè) Generator 函數(shù)碌冶,調(diào)用它會(huì)生成一個(gè)遍歷器對(duì)象g。它的Symbol.iterator屬性贡未,也是一個(gè)遍歷器對(duì)象生成函數(shù)种樱,執(zhí)行后返回它自己。這一點(diǎn)比較特殊俊卤。
我們再看一個(gè)例子
function* foo(x) {
var y = 2 * (yield (x + 1));
var z = yield (y / 3);
return (x + y + z);
}
var a = foo(5);
a.next() // Object{value:6, done:false}
a.next() // Object{value:NaN, done:false}
a.next() // Object{value:NaN, done:true}
var b = foo(5);
b.next() // { value:6, done:false }
b.next(12) // { value:8, done:false }
b.next(13) // { value:42, done:true }
這里先丟出一個(gè)結(jié)論嫩挤,然后根據(jù)這個(gè)結(jié)論,我們推導(dǎo)下(結(jié)論最好背下來)
yield表達(dá)式本身沒有返回值消恍,或者說總是返回undefined岂昭。next方法可以帶一個(gè)參數(shù),
該參數(shù)就會(huì)被當(dāng)作上一個(gè)yield表達(dá)式的返回值狠怨。
重點(diǎn)再次圈下:
- yield表達(dá)式本身沒有返回值
- next方法的一個(gè)參數(shù)是作為上一個(gè)yield的返回值约啊。
我們通過上面的代碼具體分析下:
// x = 5
var a = foo(5);
// 第一次迭代返回 x+1 為 6
a.next() // Object{value:6, done:false}
// 第二次迭代 y 為undefined, 所以 undefined / 3 是 NaN
a.next() // Object{value:NaN, done:false}
// 第三次迭代 y 為undefined, 所以z是 NaN, x是6 佣赖,所以加起來以后也是 NaN
a.next() // Object{value:NaN, done:true}
// x = 5
var b = foo(5);
// 第一次迭代返回 x+1 為 6
b.next() // { value:6, done:false }
// 注意恰矩,next 傳入?yún)?shù)12, 12 被賦予 上一個(gè)yield的返回值,也就是(yield (x + 1)) 的返回值憎蛤。此時(shí) y = 2 * 12 = 24
b.next(12) // { value:8, done:false }
// next 傳入?yún)?shù)13, 13 被賦予 上一個(gè)yield的返回值 z外傅, 所以最后返回的x+y+z =5 + 24 + 13 = 42
b.next(13) // { value:42, done:true }
更進(jìn)一步的學(xué)習(xí)可以參考下面的資料。
參考資料: