前端需要掌握的ES6生成器知識(shí)

前言

很多同學(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來處理纺涤。例如 StringArray抠忘、TypedArray撩炊、MapSet 均可以通過 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í)可以參考下面的資料。

參考資料:

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末俩檬,一起剝皮案震驚了整個(gè)濱河市萎胰,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌棚辽,老刑警劉巖技竟,帶你破解...
    沈念sama閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異屈藐,居然都是意外死亡榔组,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門联逻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來瓷患,“玉大人,你說我怎么就攤上這事遣妥。” “怎么了攀细?”我有些...
    開封第一講書人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵箫踩,是天一觀的道長爱态。 經(jīng)常有香客問我,道長境钟,這世上最難降的妖魔是什么锦担? 我笑而不...
    開封第一講書人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮慨削,結(jié)果婚禮上洞渔,老公的妹妹穿的比我還像新娘。我一直安慰自己缚态,他們只是感情好磁椒,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著玫芦,像睡著了一般浆熔。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上桥帆,一...
    開封第一講書人閱讀 49,741評(píng)論 1 289
  • 那天医增,我揣著相機(jī)與錄音,去河邊找鬼老虫。 笑死叶骨,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的祈匙。 我是一名探鬼主播忽刽,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼菊卷!你這毒婦竟也來了缔恳?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤洁闰,失蹤者是張志新(化名)和其女友劉穎歉甚,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體扑眉,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡纸泄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了腰素。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片聘裁。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖弓千,靈堂內(nèi)的尸體忽然破棺而出衡便,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布镣陕,位于F島的核電站谴餐,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏呆抑。R本人自食惡果不足惜岂嗓,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望鹊碍。 院中可真熱鬧厌殉,春花似錦、人聲如沸侈咕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽乎完。三九已至熏兄,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間树姨,已是汗流浹背摩桶。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留帽揪,地道東北人硝清。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像转晰,于是被迫代替她去往敵國和親芦拿。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容