成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

JS 用狀態(tài)機(jī)的思想看Generator之基本語法篇

moven_j / 1058人閱讀

摘要:首先是初態(tài),當(dāng)函數(shù)被執(zhí)行后,狀態(tài)機(jī)就自動(dòng)處于初態(tài)了。這一次,方法對(duì)狀態(tài)機(jī)的操作與方法大體相同。實(shí)際案例下面利用狀態(tài)機(jī)的思想講講兩個(gè)實(shí)際案例。靠著狀態(tài)機(jī)的思想,我在學(xué)習(xí)時(shí)基本

前言

最近學(xué)習(xí)了阮一峰老師的《ECMAScript 6 入門》里的Generator相關(guān)知識(shí),以及《你不知道的JS》中卷的異步編程部分。同時(shí)在SegmentFault問答區(qū)看到了一些前端朋友對(duì)Generator的語法和執(zhí)行過程有一些疑問,于是我想分享一下自己對(duì)Generator的理解,也許對(duì)前端社區(qū)會(huì)有所幫助。

Generator本質(zhì)

Generator的本質(zhì)是一個(gè)狀態(tài)機(jī),yield關(guān)鍵字的作用是分割兩個(gè)狀態(tài),右邊的語句執(zhí)行在前一個(gè)狀態(tài),而左邊的語句是下一個(gè)狀態(tài)要執(zhí)行的。如果右邊為空則默認(rèn)為undefined,左邊為空默認(rèn)為一個(gè)賦值語句,被賦值的變量永遠(yuǎn)不會(huì)被調(diào)用。當(dāng)調(diào)用Generator函數(shù)獲取一個(gè)迭代器時(shí),狀態(tài)機(jī)處于初態(tài)。迭代器調(diào)用next方法后,向下一個(gè)狀態(tài)跳轉(zhuǎn),然后執(zhí)行該狀態(tài)的代碼。當(dāng)遇到return或最后一個(gè)yield時(shí),進(jìn)入終態(tài)。終態(tài)的標(biāo)識(shí)就是next方法返回對(duì)象的done屬性。

Generator狀態(tài)跳轉(zhuǎn)

Generator函數(shù)執(zhí)行后會(huì)生出一個(gè)迭代器,包含3個(gè)主要方法:next、throw和return。它們的本質(zhì)都是改變狀態(tài)機(jī)的狀態(tài),但throw和return屬于強(qiáng)制改變,next則是按照定義好的流程去改變。下面我來分別講講這三種方法。

next方法

先看下面這個(gè)例子:

function* gen(){
    console.log("state1");
    let state1 = yield "state1";
    console.log("state2");
    let state2 = yield "state2";
    console.log("end");
}

我們聲明了一個(gè)名為gen的Generator函數(shù),其中有2個(gè)yield語句,我們可以歸納出4個(gè)狀態(tài):

初態(tài):這個(gè)狀態(tài)是gen這個(gè)“狀態(tài)機(jī)”的初始狀態(tài),什么也不會(huì)做;

狀態(tài)一:初態(tài)的下一個(gè)狀態(tài),跳轉(zhuǎn)到這個(gè)狀態(tài)后執(zhí)行

console.log("state1");
yield "state1";

狀態(tài)二:這個(gè)狀態(tài)會(huì)先接收上一個(gè)狀態(tài)傳來的數(shù)據(jù)data,然后執(zhí)行

let state1 = data;
console.log("state2");
yield "state2";//注意,這里是最后一個(gè)yield

這里的data是對(duì)上一個(gè)狀態(tài)中yield "state1"的替換。

狀態(tài)三(終態(tài)):因?yàn)間en已經(jīng)執(zhí)行過最后一個(gè)yield表達(dá)式,所以狀態(tài)三也就是狀態(tài)機(jī)的終態(tài)。這個(gè)狀態(tài)也接受了上一個(gè)狀態(tài)傳來的數(shù)據(jù)data,執(zhí)行了

let state2 = data;
console.log("end");

同時(shí),還將迭代器返回的對(duì)象done屬性修改為true,比如{value:undefined,done:true}。這代表gen這個(gè)狀態(tài)機(jī)已經(jīng)執(zhí)行到了終態(tài)。

將gen這個(gè)Generator函數(shù)轉(zhuǎn)換成狀態(tài)機(jī)以后,我們可以在腦中想象出下面這張圖:

接下來我們就根據(jù)這張圖分析下狀態(tài)間是如何跳轉(zhuǎn)的。

首先是初態(tài),當(dāng)Generator函數(shù)被執(zhí)行后,狀態(tài)機(jī)就自動(dòng)處于初態(tài)了。這個(gè)狀態(tài)并不會(huì)執(zhí)行任何語句。
也就是執(zhí)行語句:

let g = gen();

會(huì)有一個(gè)箭頭指向初態(tài),如下圖:

然后是非初態(tài)間的狀態(tài)跳轉(zhuǎn)。如果你想要按照gen里定義好的狀態(tài)順序跳轉(zhuǎn),那你應(yīng)該使用next()方法。比如我們第一次執(zhí)行g.next(),gen這個(gè)狀態(tài)機(jī)會(huì)從初態(tài)跳轉(zhuǎn)到狀態(tài)一。然后再執(zhí)行g.next(),則狀態(tài)一會(huì)向狀態(tài)二跳轉(zhuǎn),并且發(fā)送數(shù)據(jù)undefined,這是因?yàn)閚ext函數(shù)沒有傳參,默認(rèn)為undefined。關(guān)于狀態(tài)間如何傳遞數(shù)據(jù)我將在下一節(jié)講。

當(dāng)我們不斷調(diào)用next方法,gen會(huì)按照定義好的流程進(jìn)行狀態(tài)跳轉(zhuǎn)。而且即使是到了終態(tài),next也會(huì)返回對(duì)象,只是這個(gè)對(duì)象的值一直是{value:undefined,done:true}。聽上去像是在終態(tài)后面又新增了一個(gè)狀態(tài),所以next方法能夠不斷執(zhí)行。但是我覺得為了符合狀態(tài)機(jī)的設(shè)定,還是將第一個(gè)done為true的狀態(tài)叫做終態(tài)比較好。

return方法

與按部就班的next方法不同,return方法會(huì)打破原有的狀態(tài)序列,并根據(jù)開發(fā)者的需要跳轉(zhuǎn)到一個(gè)新的狀態(tài),而這個(gè)狀態(tài)有兩個(gè)特點(diǎn):

不是原有狀態(tài)序列中的任何一個(gè)狀態(tài);

該狀態(tài)返回的對(duì)象的done屬性值為true。

我們繼續(xù)用上面的例子。如果從狀態(tài)一跳轉(zhuǎn)到狀態(tài)二,使用的代碼是g.return();而不是g.next(),那么狀態(tài)圖會(huì)變成下面這個(gè)樣子:

從圖中可以看出,return的行為就是新增一個(gè)新·狀態(tài)二插入在狀態(tài)一后面,然后從狀態(tài)一跳轉(zhuǎn)到新·狀態(tài)二,同時(shí)輸出{value:undefined,done:true}。同樣,這里的undefined也是因?yàn)閞eturn方法沒有傳參。

如果Generator函數(shù)里有一個(gè)try...finally語句,return新建的狀態(tài)會(huì)插入在執(zhí)行finally塊最后一行語句的狀態(tài)之后??梢钥纯催@一節(jié)阮一峰老師舉的例子。

throw方法

我喜歡將throw方法當(dāng)作next和return方法的結(jié)合。throw()方法與throw關(guān)鍵字很像,都是拋出一個(gè)錯(cuò)誤。而Generator函數(shù)會(huì)根據(jù)是否定義捕獲語句來進(jìn)行狀態(tài)跳轉(zhuǎn)。一共有下面3種情況:

沒有try...catch;

下一個(gè)狀態(tài)要執(zhí)行的語句在try...catch中;

throw()方法在一個(gè)try...catch中被調(diào)用。

沒有try...catch

繼續(xù)使用上一章的代碼,假設(shè)從狀態(tài)一到狀態(tài)二使用的是g.throw()

function* gen(){
    console.log("state1");
    let state1 = yield "state1";
    console.log("state2");
    let state2 = yield "state2";
    console.log("end");
}
let g = gen();
g.next();
g.throw();

首先,狀態(tài)二的代碼console.log("state2");...并不在try...catch塊中,而且也不是在try...catch塊中調(diào)用g.throw()。那么最后的狀態(tài)圖應(yīng)該是下面這樣:

看上去就像是調(diào)用了return方法,新增一個(gè)狀態(tài),同時(shí)將輸出的對(duì)象done屬性設(shè)置為true。但是有一點(diǎn)不同的是這個(gè)對(duì)象并不會(huì)輸出,而是報(bào)錯(cuò):Uncaught undefined,因?yàn)槌绦蛞蝈e(cuò)誤而中斷。同樣,原本要輸出的字符串state2也不會(huì)輸出。

這里我認(rèn)為需要重視的一個(gè)問題是錯(cuò)誤是在狀態(tài)二中的哪一條語句拋出的?修改了代碼位置后,我發(fā)現(xiàn)throw()方法是將yield "state1"替換成throw undefined,所以之后的let state1...等語句都不會(huì)執(zhí)行。

下一個(gè)狀態(tài)在try...catch中

修改上一章的示例代碼:

function* gen(){
    console.log("state1");
    try{
        let state1 = yield "state1";
        console.log("state2");
    }catch(e){
        console.log("catch it");
    }
    let state2 = yield "state2";
    console.log("end");
}
let g = gen();
g.next();
g.throw();

由于狀態(tài)二要執(zhí)行的代碼被try...catch包裹,所以throw()拋出的錯(cuò)誤被catch塊捕獲,從而程序直接轉(zhuǎn)入catch塊執(zhí)行語句,打印“catch it”。這與JS的錯(cuò)誤捕獲機(jī)制一致,狀態(tài)圖總體并不會(huì)變化,只是狀態(tài)二節(jié)點(diǎn)下的執(zhí)行語句有變化。

注意紅色圈內(nèi)的語句,相比較與調(diào)用next方法時(shí)的狀態(tài)二,刪除了try塊中錯(cuò)誤拋出位置后的let state1 = data;console.log("state2");,添加了catch塊中要執(zhí)行的console.log("catch it");,如果有finally塊也會(huì)把里面的語句添加進(jìn)去。之后再調(diào)用next方法,仍然會(huì)按照規(guī)定好的流程進(jìn)行跳轉(zhuǎn)。

這一次,throw方法對(duì)狀態(tài)機(jī)的操作與next方法大體相同。但因?yàn)樗举|(zhì)上是拋出錯(cuò)誤,所以會(huì)對(duì)程序的代碼執(zhí)行順序有一定的影響。

throw()方法在一個(gè)try...catch中被調(diào)用

只要結(jié)合上面2種情況,記住3個(gè)規(guī)則就行:

Genereator內(nèi)部沒有try...catch則當(dāng)作正常拋出錯(cuò)誤處理;

下一個(gè)狀態(tài)在try...catch中時(shí),throw()方法拋出的錯(cuò)誤會(huì)被捕獲,那相當(dāng)于外部沒有捕獲錯(cuò)誤,與第二種情況一致。

規(guī)則2中錯(cuò)誤捕獲后的狀態(tài)執(zhí)行代碼報(bào)錯(cuò),按規(guī)則1處理。

這里,針對(duì)規(guī)則3做一個(gè)講解。

看下面這個(gè)例子:

function* gen(){
    console.log("state1");
    try{
        let state1 = yield "state1";
        console.log("state2");
    }catch(e){
        err = a;//錯(cuò)誤
        console.log("內(nèi)部捕獲");
    }
    let state2 = yield "state2";
    console.log("end");
}
let g = gen();
g.next();
try{
    g.throw();
}catch(e){
    console.log("外部捕獲");
}

那么原本符合規(guī)則2的代碼在捕獲throw()拋出的錯(cuò)誤后又因?yàn)闆]有聲明標(biāo)識(shí)符a報(bào)錯(cuò),從而被外層catch塊捕獲。導(dǎo)致看上去就像規(guī)則1一樣。

狀態(tài)間傳值

next、throw和return方法除了狀態(tài)跳轉(zhuǎn)外,還有一個(gè)功能就是為前后兩個(gè)狀態(tài)傳值。但是它們3個(gè)的表現(xiàn)又各不相同。

next給狀態(tài)傳值的表現(xiàn)中規(guī)中矩,看看下面的代碼:

function* gen(){
    let value = yield "你好";
    console.log(value);
}
let g = gen();
g.next();
g.next("再見");

當(dāng)我們想要跳轉(zhuǎn)到執(zhí)行console.log(value);的狀態(tài)二時(shí),給next方法傳一個(gè)字符串“再見”,然后yield "你好"會(huì)被替換成"再見",賦值給value變量打印出來。你可以試試不傳值或者傳其他值,應(yīng)該能幫助你理解更深刻。

throw方法一般都會(huì)傳值,而且為了規(guī)范應(yīng)該傳一個(gè)Error對(duì)象。

return方法傳值有點(diǎn)特殊,修改上面的代碼:

function* gen(){
    let value = yield "你好";
    console.log(value);
}
let g = gen();
g.next();
g.return("看得見我嗎?");

如果你前面的知識(shí)沒忘的話,你應(yīng)該知道,用return替換next后,什么也不會(huì)打印。因?yàn)樘D(zhuǎn)到了一個(gè)什么代碼也不會(huì)執(zhí)行狀態(tài)。那么return函數(shù)的參數(shù)作用體現(xiàn)在哪呢?還記得每一個(gè)方法調(diào)用后都會(huì)返回一個(gè)對(duì)象嗎?上面的代碼輸出了{value:"看得見我嗎",done:true}。哈,我看見你了。

關(guān)于終態(tài)

一般我喜歡把最后一個(gè)yield或是return表達(dá)式當(dāng)作最后一個(gè)狀態(tài)。但是有時(shí)候可以把終態(tài)想象成一個(gè)不斷循環(huán)自身的狀態(tài),比如下面這樣:

這樣理解有一個(gè)好處是可以解釋為什么done屬性值為true后,再次調(diào)用next仍會(huì)返回一個(gè)對(duì)象{value:undefined,done:true}。但是這樣會(huì)多一個(gè)狀態(tài),畫圖不方便(假裝這個(gè)理由很充分)。
總之,如何理解全看個(gè)人喜歡。

實(shí)際案例

下面利用狀態(tài)機(jī)的思想講講兩個(gè)實(shí)際案例。

一個(gè)小問題

我之前回答過一個(gè)問題,把它當(dāng)作實(shí)例來分析一下吧

題主不太理解下面代碼的執(zhí)行順序:

function* bar() {
  console.log("one");
  console.log("two");
  console.log("three");
  yield console.log("test");
  console.log(`1. ${yield}`);
  console.log(`2. ${yield}`);
  return "result";
}
let barObj = bar();
barObj.next();
barObj.next("a");
barObj.next("b");

讓我們來幫他分析分析吧。

首先,我補(bǔ)全了這段代碼。

function* bar() {
  console.log("one");
  console.log("two");
  console.log("three");
  yield console.log("test");
  console.log(`1. ${yield}`);
  console.log(`2. ${yield}`);
  return "result";
}
let barObj = bar();
barObj.next();
barObj.next("a");
barObj.next("b");
barObj.next("c");
barObj.next();

然后,分析bar這個(gè)Genereator聲明了幾個(gè)狀態(tài)。一共有6個(gè)狀態(tài),狀態(tài)圖如下:

根據(jù)狀態(tài)圖,題主提出的兩個(gè)問題:

第一次 next 的時(shí)候應(yīng)該走到了 yield console.log("test")

第二次傳了一個(gè) a 這個(gè)時(shí)候程序似乎沒有執(zhí)行

第一個(gè)問題,調(diào)用next方法后,跳轉(zhuǎn)到state1,而yield console.log("test")是在state1里執(zhí)行的,所以確實(shí)走到了這行代碼。

然后,調(diào)用next("a"),跳轉(zhuǎn)到state2,這里并沒有值接收字符串"a",所以自然沒有打印出來,造成程序沒有執(zhí)行的假象。

這個(gè)問題比較簡(jiǎn)單,狀態(tài)圖一畫就能理解了。

throw方法的一個(gè)特性

第二個(gè)實(shí)例是我在看《ECMAScript 6 入門》時(shí),阮一峰老師說:

throw方法被捕獲以后,會(huì)附帶執(zhí)行下一條yield表達(dá)式。也就是說,會(huì)附帶執(zhí)行一次next方法。

然后舉了一個(gè)例子:

var gen = function* gen(){
  try {
    yield console.log("a");
  } catch (e) {
    // ...
  }
  yield console.log("b");
  yield console.log("c");
}

var g = gen();
g.next() // a
g.throw() // b
g.next() // c

這里我覺得很奇怪,因?yàn)榘凑瘴业南敕?,這是顯然的呀,為什么要多帶帶說呢?按照我在Generator狀態(tài)跳轉(zhuǎn)那一章說的,這屬于下一個(gè)狀態(tài)在try...catch中的情況,因?yàn)?/p>

try{
    /*state2*/yield console.log("a");
}

中yield的左側(cè)是state2狀態(tài)的代碼,雖然沒有寫,但是我們默認(rèn)為向一個(gè)永遠(yuǎn)不會(huì)被調(diào)用的變量進(jìn)行賦值。
接著是畫狀態(tài)圖:

我們只關(guān)心g.throw(),所以畫部分狀態(tài)圖就夠了。從圖中可以看出,throw方法被調(diào)用后,因?yàn)殄e(cuò)誤被捕獲,所以正常跳轉(zhuǎn)到了state2,然后必然會(huì)執(zhí)行yield console.log("b");。

總結(jié)

狀態(tài)機(jī)的知識(shí)還是在大學(xué)的編譯原理課學(xué)習(xí)的,有些概念已經(jīng)忘了。不過在看Generator時(shí),我突然覺得用狀態(tài)機(jī)來解釋代碼的凍結(jié)和執(zhí)行非常直觀。只要能夠畫出相應(yīng)的狀態(tài)圖就可以知道每一次調(diào)用next等方法會(huì)執(zhí)行什么樣的代碼??恐鵂顟B(tài)機(jī)的思想,我在學(xué)習(xí)Generator時(shí)基本沒有疑惑,所以決定整理并分享出來。
但是我有點(diǎn)不自信,因?yàn)榫W(wǎng)上搜索了很多次,除了阮一峰老師,并沒有人同時(shí)提到狀態(tài)機(jī)和Generator兩個(gè)關(guān)鍵字。我在寫這篇文章的時(shí)候也偶爾懷疑是不是我錯(cuò)了。不過既然已經(jīng)寫了這么多,而且從我自身感覺以及解決了文中兩個(gè)例子的情況來看,分享出來讓大家指指錯(cuò)也是不錯(cuò)的。 所以,如果有什么問題希望能夠在評(píng)論中指出。非常感謝你的閱讀,祝你新年快樂!

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/107426.html

相關(guān)文章

  • 探索Javascript 異步編程

    摘要:因?yàn)闉g覽器環(huán)境里是單線程的,所以異步編程在前端領(lǐng)域尤為重要。除此之外,它還有兩個(gè)特性,使它可以作為異步編程的完整解決方案函數(shù)體內(nèi)外的數(shù)據(jù)交換和錯(cuò)誤處理機(jī)制。 showImg(https://segmentfault.com/img/bVz9Cy); 在我們?nèi)粘>幋a中,需要異步的場(chǎng)景很多,比如讀取文件內(nèi)容、獲取遠(yuǎn)程數(shù)據(jù)、發(fā)送數(shù)據(jù)到服務(wù)端等。因?yàn)闉g覽器環(huán)境里Javascript是單線程的,...

    Salamander 評(píng)論0 收藏0
  • JS筆記

    摘要:從最開始的到封裝后的都在試圖解決異步編程過程中的問題。為了讓編程更美好,我們就需要引入來降低異步編程的復(fù)雜性。異步編程入門的全稱是前端經(jīng)典面試題從輸入到頁面加載發(fā)生了什么這是一篇開發(fā)的科普類文章,涉及到優(yōu)化等多個(gè)方面。 TypeScript 入門教程 從 JavaScript 程序員的角度總結(jié)思考,循序漸進(jìn)的理解 TypeScript。 網(wǎng)絡(luò)基礎(chǔ)知識(shí)之 HTTP 協(xié)議 詳細(xì)介紹 HTT...

    rottengeek 評(píng)論0 收藏0
  • ES6 Generator實(shí)現(xiàn)協(xié)同程序

    摘要:關(guān)鍵字表示代碼在該處將會(huì)被阻塞式暫停阻塞的僅僅是函數(shù)代碼本身,而不是整個(gè)程序,但是這并沒有引起函數(shù)內(nèi)部自頂向下代碼的絲毫改變。通過實(shí)現(xiàn)模式在通過實(shí)現(xiàn)理論的過程中已經(jīng)有一些有趣的探索了。 至此本系列的四篇文章翻譯完結(jié),查看完整系列請(qǐng)移步blogs 由于個(gè)人能力知識(shí)有限,翻譯過程中難免有紕漏和錯(cuò)誤,望不吝指正issue ES6 Generators: 完整系列 The Basics...

    MudOnTire 評(píng)論0 收藏0
  • ES6-7

    摘要:的翻譯文檔由的維護(hù)很多人說,阮老師已經(jīng)有一本關(guān)于的書了入門,覺得看看這本書就足夠了。前端的異步解決方案之和異步編程模式在前端開發(fā)過程中,顯得越來越重要。為了讓編程更美好,我們就需要引入來降低異步編程的復(fù)雜性。 JavaScript Promise 迷你書(中文版) 超詳細(xì)介紹promise的gitbook,看完再不會(huì)promise...... 本書的目的是以目前還在制定中的ECMASc...

    mudiyouyou 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

moven_j

|高級(jí)講師

TA的文章

閱讀更多
最新活動(dòng)
閱讀需要支付1元查看
<