溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

ES6中Generator函數(shù)的使用方法

發(fā)布時(shí)間:2020-10-10 17:46:45 來(lái)源:億速云 閱讀:129 作者:小新 欄目:web開(kāi)發(fā)

這篇文章主要介紹了ES6中Generator函數(shù)的使用方法,具有一定借鑒價(jià)值,需要的朋友可以參考下。希望大家閱讀完這篇文章后大有收獲。下面讓小編帶著大家一起了解一下。

一、什么是Generator函數(shù)

Generator函數(shù)是ES6標(biāo)準(zhǔn)中提出的一種異步編程的解決方案。這種函數(shù)與普通函數(shù)最大的區(qū)別在于它可以暫停執(zhí)行,又可以從暫停的位置恢復(fù)繼續(xù)執(zhí)行。

從語(yǔ)法上看,Generator函數(shù)就是一個(gè)狀態(tài)機(jī),封裝了許多內(nèi)部狀態(tài)。

從實(shí)質(zhì)上看,Generator函數(shù)就是一個(gè)遍歷器對(duì)象生成器。(關(guān)于遍歷器對(duì)象,可以參考阮一峰老師的這篇文章)Generator函數(shù)返回一個(gè)遍歷器對(duì)象,遍歷這個(gè)對(duì)象,就可以依次得到函數(shù)內(nèi)部的每一個(gè)狀態(tài)。

二、基本語(yǔ)法

1、定義Generator函數(shù)

定義一個(gè)Generator函數(shù)和定義一個(gè)普通函數(shù)的區(qū)別在于:

function關(guān)鍵字和函數(shù)名之間有一個(gè) *(星號(hào))。
函數(shù)內(nèi)部使用yield來(lái)定義每一個(gè)函數(shù)內(nèi)部的狀態(tài)。
如果函數(shù)內(nèi)部有return語(yǔ)句,那么他就是函數(shù)內(nèi)部的最后一個(gè)狀態(tài)。

來(lái)看一個(gè)簡(jiǎn)單的例子:

// 定義
function* sayHello() {
  yield 'hello';
  yield 'world';
  return 'ending';
}
// 調(diào)用
// 注意,hw獲取到的值是一個(gè)遍歷器對(duì)象
let g = sayHello();

上面的例子,定義了一個(gè)名為sayHello的Generator函數(shù),它內(nèi)部有兩個(gè)yield表達(dá)式和一個(gè)return表達(dá)式。所以,該函數(shù)內(nèi)部有三個(gè)狀態(tài):hello,worldreturn語(yǔ)句(結(jié)束執(zhí)行)。最后,調(diào)用這個(gè)函數(shù),得到一個(gè)遍歷器對(duì)象并賦值給變量g。

Generator函數(shù)的調(diào)用方法與普通函數(shù)完全一樣,函數(shù)名()。不同的是:

  • 函數(shù)調(diào)用后,內(nèi)部代碼(從第一行開(kāi)始)都不會(huì)立即執(zhí)行。
  • 函數(shù)調(diào)用后會(huì)有一個(gè)返回值,這個(gè)值是一個(gè)指向內(nèi)部狀態(tài)的指針對(duì)象,實(shí)質(zhì)就是一個(gè)包含函數(shù)內(nèi)部狀態(tài)的遍歷器對(duì)象。

Generator函數(shù)調(diào)用后不會(huì)立即執(zhí)行,那么,我們?nèi)绾巫屗_(kāi)始執(zhí)行內(nèi)部的代碼呢?又如何獲取它內(nèi)部的每一個(gè)狀態(tài)呢?此時(shí),我們必須調(diào)用返回的生成器對(duì)象的.next()方法,才能開(kāi)始代碼的執(zhí)行,并且使得指針移向下一個(gè)狀態(tài)。

以上面的例子為例:

g.next();
// { value: 'hello', done: false }
g.next();
// { value: 'world', done: false }
g.next();
// { value: 'ending', done: true }
g.next();
// { value: undefined, done: true }

上面的代碼中,一共調(diào)用了四次g這個(gè)遍歷器對(duì)象的.next()方法。第一次調(diào)用,sayHello這個(gè)Generator函數(shù)開(kāi)始執(zhí)行,直到遇到第一個(gè)yield表達(dá)式就會(huì)暫停執(zhí)行。.next()方法會(huì)返回一個(gè)對(duì)象,它的value屬性就是當(dāng)前yield表達(dá)式的值hello,done屬性的值false,表示遍歷還沒(méi)有結(jié)束。

第二次再調(diào)用.next(),就會(huì)執(zhí)行到第二個(gè)yield表達(dá)式處,并暫停執(zhí)行,返回對(duì)應(yīng)的對(duì)象。

第三次調(diào)用.next(),函數(shù)執(zhí)行到最后的return語(yǔ)句,此時(shí)標(biāo)志著遍歷器對(duì)象g遍歷結(jié)束,所以返回的對(duì)象中value屬性值就是return后面所跟的值ending,done屬性值為true,表示遍歷已經(jīng)結(jié)束。

第四次以及后面在調(diào)用.next()方法,返回的都會(huì)是{value: undefined, done: true }。

2、yield表達(dá)式

由Generator函數(shù)返回的遍歷器對(duì)象,只有調(diào)用.next()方法才會(huì)遍歷到下一個(gè)內(nèi)部狀態(tài),所以這其實(shí)是提供了一種可以暫停執(zhí)行的函數(shù),yield表達(dá)式就是暫停標(biāo)志。

遍歷器對(duì)象的.next()方法的運(yùn)行邏輯如下。

  1. 遇到yield表達(dá)式,就暫停執(zhí)行后面的操作,并將緊跟在yield后面的那個(gè)表達(dá)式的值,作為返回的對(duì)象的value屬性值。
  2. 下一次調(diào)用.next()方法時(shí),再繼續(xù)往下執(zhí)行,直到遇到下一個(gè)yield表達(dá)式。
  3. 如果沒(méi)有再遇到新的yield表達(dá)式,就一直運(yùn)行到函數(shù)結(jié)束,直到return語(yǔ)句為止,并將return語(yǔ)句后面的表達(dá)式的值,作為返回的對(duì)象的value屬性值。
  4. 如果該函數(shù)沒(méi)有return語(yǔ)句,則返回的對(duì)象的value屬性值為undefined。

值得注意的是:

  1. yield關(guān)鍵字只能出現(xiàn)在Generator函數(shù)中,出現(xiàn)在別的函數(shù)中會(huì)報(bào)錯(cuò)。
  // 出現(xiàn)在普通函數(shù)中,報(bào)錯(cuò)
  (function () {
    yield 'hello';
  })()

  // forEach不是Generator函數(shù),報(bào)錯(cuò)
  [1, 2, 3, 4, 5].forEach(val => {
    yield val
  });
  1. yield關(guān)鍵字后面跟的表達(dá)式,是惰性求值的。 只有當(dāng)調(diào)用.next()方法、內(nèi)部狀態(tài)暫停到當(dāng)前yield時(shí),才會(huì)計(jì)算其后面跟的表達(dá)式的值。這等于為JavaScript提供了手動(dòng)的“惰性求值”的語(yǔ)法功能。
function* step() {
  yield 'step1';

  // 下面的yield后面的表達(dá)式不會(huì)立即求值,
  // 只有暫停到這一行時(shí),才會(huì)計(jì)算表達(dá)式的值。
  yield 'step' + 2;

  yield 'setp3';
  return 'end';
}
  1. yield表達(dá)式本身是沒(méi)有返回值的,或者說(shuō)它的返回值為undefined。使用.next()傳參可以為其設(shè)置返回值。(后面會(huì)講到)
function* gen() {
  for (let i = 0; i < 5; i++) {
    let res = yield;  // yield表達(dá)式本身沒(méi)有返回值
    console.log(res); // undefined
  }
}
let g = gen();
g.next();   // {value: 0, done: false}
g.next();   // {value: 1, done: false}
g.next();   // {value: 2, done: false}

yield與return的異同:

相同點(diǎn):

  • 兩者都能返回跟在其后面的表達(dá)式的值。

不同點(diǎn):

  • yield表達(dá)式只是暫停函數(shù)向后執(zhí)行,return是直接結(jié)束函數(shù)執(zhí)行。
  • yield表達(dá)式可以出現(xiàn)多次,后面還可以有代碼。return只能出現(xiàn)一次,后面的代碼不會(huì)執(zhí)行,在一些情況下還會(huì)報(bào)錯(cuò)。
  • 正常函數(shù)只能返回一個(gè)值,因?yàn)橹荒軋?zhí)行一次return。Generator函數(shù)可以返回一系列的值,因?yàn)榭梢杂腥我舛鄠€(gè)yield。
3、.next()方法傳參

前面我們說(shuō)到過(guò),yield表達(dá)式自身沒(méi)有返回值,或者說(shuō)返回值永遠(yuǎn)是undefined。但是,我們可以通過(guò)給.next()方法傳入一個(gè)參數(shù),來(lái)設(shè)置上一個(gè)(是上一個(gè))yield表達(dá)式返回值。

來(lái)看一個(gè)例子:

function* conoleNum() {
  console.log('Started');
  console.log(`data: ${yield}`);
  console.log(`data: ${yield}`);
  return 'Ending';
}
let g = conoleNum();

g.next();      // 控制臺(tái)輸出:'Started'

g.next('a');   // 控制臺(tái)輸出:'data: a'
// 不傳入?yún)?shù)'a',就會(huì)輸出'data: undefined'

g.next('b');   // 控制臺(tái)輸出:'data: b'
// 不傳入?yún)?shù)'a',就會(huì)輸出'data: undefined'

上面的例子,需要強(qiáng)調(diào)一個(gè)不易理解的地方。

第一次調(diào)用.next(),此時(shí)函數(shù)暫停在代碼第三行的yield表達(dá)式處。記得嗎?yield會(huì)暫停函數(shù)執(zhí)行,此時(shí)打印它的console.log(),也就是代碼第三行的console,由于暫停并沒(méi)有被執(zhí)行,所以不會(huì)打印出結(jié)果,只輸出了代碼第二行的'Started'。

當(dāng)?shù)诙握{(diào)用.next()方法時(shí),傳入?yún)?shù)'a',函數(shù)暫停在代碼第四行的yield語(yǔ)句處。此時(shí)參數(shù)'a'會(huì)被當(dāng)做上一個(gè)yield表達(dá)式的返回值,也就是代碼第三行的yiled表達(dá)式的返回值,所以此時(shí)控制臺(tái)輸出'data: a'。而代碼第四行的console.log()由于暫停,沒(méi)有被輸出。

第三次調(diào)用,同理。所以輸出'data: b'。

4、Generator.prototype.throw()

Generator函數(shù)返回的遍歷器對(duì)象,都有一個(gè).throw()方法,可以在函數(shù)體外拋出錯(cuò)誤,然后在Generator函數(shù)體內(nèi)捕獲。

function* gen() {
  try {
    yield;
  } catch (e) {
    console.log('內(nèi)部捕獲', e);
  }
};

var g = gen();
// 下面執(zhí)行一次.next()
// 是為了讓gen函數(shù)體執(zhí)行進(jìn)入try語(yǔ)句中的yield處
// 這樣拋出錯(cuò)誤,gen函數(shù)內(nèi)部的catch語(yǔ)句才能捕獲錯(cuò)誤
g.next();

try {
  g.throw('a');
  g.throw('b');
} catch (e) {
  console.log('外部捕獲', e);
}

上面例子中,遍歷器對(duì)象ggen函數(shù)體外連續(xù)拋出兩個(gè)錯(cuò)誤。第一個(gè)錯(cuò)誤被gen函數(shù)體內(nèi)的catch語(yǔ)句捕獲。g第二次拋出錯(cuò)誤,由于gen函數(shù)內(nèi)部的catch語(yǔ)句已經(jīng)執(zhí)行過(guò)了,不會(huì)再捕捉到這個(gè)錯(cuò)誤了,所以這個(gè)錯(cuò)誤就會(huì)被拋出gen函數(shù)體,被函數(shù)體外的catch語(yǔ)句捕獲。

值得注意的是:

  • 如果Generator函數(shù)內(nèi)部沒(méi)有部署try...catch代碼塊,那么遍歷器對(duì)象的throw方法拋出的錯(cuò)誤,將被外部try...catch代碼塊捕獲。
  • 如果Generator函數(shù)內(nèi)部和外部都沒(méi)有部署try...catch代碼塊,那么程序?qū)?bào)錯(cuò),直接中斷執(zhí)行。

遍歷器對(duì)象的throw方法被捕獲以后,會(huì)附帶執(zhí)行一次.next()方法,代碼執(zhí)行會(huì)暫停到下一條yield表達(dá)式處。看下面這個(gè)例子:

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

g.next();   // 控制臺(tái)輸出:'a'

g.throw('Error');  // 控制臺(tái)輸出:'b'
// throw的錯(cuò)誤被內(nèi)部catch語(yǔ)句捕獲,
// 會(huì)自動(dòng)在執(zhí)行一次g.next()

g.next();   // 控制臺(tái)輸出:'c'
5、Generator.prototype.return()

Generator函數(shù)返回的遍歷器對(duì)象,還有一個(gè).return()方法,可以返回給定的值,并且直接結(jié)束對(duì)遍歷器對(duì)象的遍歷。

function* gen() {
  yield 1;
  yield 2;
  yield 3;
}
var g = gen();

g.next();        // { value: 1, done: false }

// 提前結(jié)束對(duì)g的遍歷。盡管yield還沒(méi)有執(zhí)行完
// 此時(shí)done屬性值為true,說(shuō)明遍歷結(jié)束
g.return('foo'); // { value: "foo", done: true }

g.next();        // { value: undefined, done: true }

如果.return()方法調(diào)用時(shí),不提供參數(shù),則返回值的value屬性為undefined。

6、yield* 表達(dá)式

yield* 用來(lái)在一個(gè)Generator函數(shù)里面執(zhí)行另一個(gè)Generator函數(shù)。

如果在一個(gè)Generator函數(shù)內(nèi)部,直接調(diào)用另一個(gè)Generator函數(shù),默認(rèn)情況下是沒(méi)有效果的。

function* gen1() {
  yield 'a';
  yield 'b';
}
function* gen2() {
  yield 'x';
  // 直接調(diào)用gen1()
  gen1();
  yield 'y';
}
// 遍歷器對(duì)象可以使用for...of遍歷所有狀態(tài)
for (let v of gen2()){
  只輸出了gen1的狀態(tài)
  console.log(v);   // 'x' 'y'
}

上面的例子中,gen1gen2都是Generator函數(shù),在gen2里面直接調(diào)用gen1,是不會(huì)有效果的。

這個(gè)就需要用到 yield* 表達(dá)式。

function* gen1() {
  yield 'a';
  yield 'b';
}
function* gen2() {
  yield 'x';
  // 用 yield* 調(diào)用gen1()
  yield* gen1();
  yield 'y';
}

for (let v of gen2()){
  輸出了gen1、gen2的狀態(tài)
  console.log(v);   // 'x' 'a' 'b' 'y'
}

感謝你能夠認(rèn)真閱讀完這篇文章,希望小編分享ES6中Generator函數(shù)的使用方法內(nèi)容對(duì)大家有幫助,同時(shí)也希望大家多多支持億速云,關(guān)注億速云行業(yè)資訊頻道,遇到問(wèn)題就找億速云,詳細(xì)的解決方法等著你來(lái)學(xué)習(xí)!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI