如何理解JavaScript中的Generators

如何理解JavaScript中的Generators,針對這個問題,這篇文章詳細介紹了相對應(yīng)的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。

成都創(chuàng)新互聯(lián)公司專注于英山企業(yè)網(wǎng)站建設(shè),響應(yīng)式網(wǎng)站建設(shè),商城網(wǎng)站建設(shè)。英山網(wǎng)站建設(shè)公司,為英山等地區(qū)提供建站服務(wù)。全流程定制網(wǎng)站建設(shè),專業(yè)設(shè)計,全程項目跟蹤,成都創(chuàng)新互聯(lián)公司專業(yè)和態(tài)度為您提供的服務(wù)

簡介

JavaScript在ES6時引入了生成器。生成器函數(shù)與常規(guī)函數(shù)類似,除了可以暫停和恢復(fù)它們這一點以外。生成器也與迭代器密切相關(guān),因為生成器對象就是迭代器。
在JavaScript中,函數(shù)調(diào)用后通常不能暫?;蛲V?。(是的,異步函數(shù)在等待await語句時暫停,但是異步函數(shù)在ES7時才引入。此外,異步函數(shù)是建立在生成器之上的。)一個普通函數(shù)只有在返回或拋出錯誤時才會結(jié)束。

function foo() {
  console.log('Starting');
  const x = 42;
  console.log(x);  
  console.log('Stop me if you can');  
  console.log('But you cannot');
 }

相反,生成器允許我們在任意斷點處暫停執(zhí)行,并從同一斷點恢復(fù)執(zhí)行。

生成器和迭代器

來自MDN:

在JavaScript中,迭代器是一個對象,它定義一個序列,并在終止時可能返回一個返回值。 >更具體地說,迭代器是通過使用 next() 方法實現(xiàn) Iterator protocol >的任何一個對象,該方法返回具有兩個屬性的對象: value,這是序列中的 next 值;和 done, 如果已經(jīng)迭代到序列中的最后一個值,則它為 true 。如果 value 和 done 一起存在,則它是迭代器的返回值。

因此,迭代器的本質(zhì)就是:

  • 定義序列的對象

  • 有一個next()方法…

  • 返回一個具有兩個屬性的對象:value和done

是否需要生成器來創(chuàng)建迭代器?不。事實上,我們已經(jīng)可以使用閉包pre-ES6創(chuàng)建一個無限的斐波那契數(shù)列,如下例所示:

var fibonacci = {
  next: (function () {
    var pre = 0, cur = 1;
    return function () {
      tmp = pre;
      pre = cur;
      cur += tmp;
      return cur;
    };
  })()
};

fibonacci.next(); // 1
fibonacci.next(); // 2
fibonacci.next(); // 3
fibonacci.next(); // 5
fibonacci.next(); // 8

關(guān)于生成器的好處,我將再次引用MDN:

雖然自定義迭代器是一個有用的工具,但是由于需要顯式地維護它們的內(nèi)部狀態(tài),創(chuàng)建它們需要我們仔細地編程。生成器函數(shù)提供了一個強大的替代方法:它們允許我們通過編寫一個執(zhí)行不是連續(xù)的函數(shù)來定義迭代算法。
換句話說,使用生成器創(chuàng)建迭代器更簡單(不需要閉包!),這意味著出錯的可能性更小。
生成器和迭代器之間的關(guān)系就是生成器函數(shù)返回的生成器對象是迭代器。

語法

生成器函數(shù)使用function *語法創(chuàng)建,并使用yield關(guān)鍵字暫停。  
最初調(diào)用生成器函數(shù)并不執(zhí)行它的任何代碼;相反,它返回一個生成器對象。該值通過調(diào)用生成器的next()方法來使用,該方法執(zhí)行代碼,直到遇到y(tǒng)ield關(guān)鍵字,然后暫停,直到再次調(diào)用next()。

function * makeGen() {
  yield 'Hello';
  yield 'World';
}

const g = makeGen(); // g is a generator
g.next(); // { value: 'Hello', done: false }
g.next(); // { value: 'World', done: false }
g.next(); // { value: undefined, done: true }

在上面的最后一個語句之后重復(fù)調(diào)用g.next()只會返回(或者更準(zhǔn)確地說,產(chǎn)生)相同的返回對象:{ value: undefined, done: true }。

yield暫停執(zhí)行

大家可能會注意到上面的代碼片段有一些特殊之處。第二個next()調(diào)用生成一個對象,該對象的屬性為done: false,而不是done: true。  
既然我們正在生成器函數(shù)中執(zhí)行最后一條語句,那么done屬性不應(yīng)該為true嗎?并不是的。當(dāng)遇到y(tǒng)ield語句時,它后面的值(在本例中是“World”)被生成,執(zhí)行暫停。因此,第二個next()調(diào)用暫停在第二個yield語句上,因此執(zhí)行還沒有完成—只有在第二個yield語句之后執(zhí)行重新開始時,執(zhí)行才算完成(即done: true),并且不再運行代碼。  
我們可以將next()調(diào)用看作是告訴程序運行到下一個yield語句(假設(shè)它存在)、生成一個值并暫停。程序在恢復(fù)執(zhí)行之前不會知道yield語句之后沒有任何內(nèi)容,并且只能通過另一個next()調(diào)用恢復(fù)執(zhí)行。

yield和return

在上面的示例中,我們使用yield將值傳遞給生成器外部。我們也可以使用return(就像在普通函數(shù)中一樣);但是,使用return可以終止執(zhí)行并設(shè)置done: true。

function * makeGen() {
  yield 'Hello';
  return 'Bye';
  yield 'World';
}

const g = makeGen(); // g is a generator
g.next(); // { value: 'Hello', done: false }
g.next(); // { value: 'Bye', done: true }
g.next(); // { value: undefined, done: true }

因為執(zhí)行不會在return語句上暫停,而且根據(jù)定義,在return語句之后不能執(zhí)行任何代碼,所以done被設(shè)置為true。

yield:next方法的參數(shù)

到目前為止,我們一直在使用yield傳遞生成器外部的值(并暫停其執(zhí)行)。  
然而,yield實際上是雙向的,并且允許我們將值傳遞到生成器函數(shù)中。

function * makeGen() {
  const foo = yield 'Hello world';
  console.log(foo);
}

const g = makeGen();
g.next(1); // { value: 'Hello world', done: false }
g.next(2); // logs 2, yields { value: undefined, done: true }

等一下。不應(yīng)該是"1"打印到控制臺,但是控制臺打印的是"2"?起初,我發(fā)現(xiàn)這部分在概念上與直覺相反,因為我預(yù)期的賦值foo = 1。畢竟,我們將“1”傳遞到next()方法調(diào)用中,從而生成Hello world,對嗎?  
但事實并非如此。傳遞給第一個next(...)調(diào)用的值將被丟棄。除了這似乎是ES6規(guī)范之外,實際上沒有其他原因.從語義上講,第一個next方法用來啟動遍歷器對象,所以不用帶有參數(shù)。
我喜歡這樣對程序的執(zhí)行進行合理化:

  • 在第一個next()調(diào)用時,它將一直運行,直到遇到y(tǒng)ield 'Hello world',在此基礎(chǔ)上生成{ value: 'Hello world', done: false }和暫停。就是這么回事。正如大家所看到的,傳遞給第一個next()調(diào)用的任何值都是不會被使用的(因此被丟棄)。

  • 當(dāng)再次調(diào)用next(...)時,執(zhí)行將恢復(fù)。在這種情況下,執(zhí)行需要為常量foo分配一些值(由yield語句決定)。因此,我們對next(2)的第二次調(diào)用賦值foo=2。程序不會在這里停止—它會一直運行,直到遇到下一個yield或return語句。在本例中,沒有更多的yield,因此它記錄2并返回undefined的done: true。在生成器使用異步因為yield是一個雙向通道,允許信息在兩個方向上流動,所以它允許我們以非??岬姆绞绞褂蒙善?。到目前為止,我們主要使用yield在生成器之外傳遞值。但是我們也可以利用yield的雙向特性以同步方式編寫異步函數(shù)。

使用上面的概念,我們可以創(chuàng)建一個類似于同步代碼但實際上執(zhí)行異步函數(shù)的基本函數(shù):

function request(url) {
  fetch(url).then(res => {
    it.next(res); // Resume iterator execution
  });
}

function * main() {
  const rawResponse = yield request('https://some-url.com');
  const returnValue = synchronouslyProcess(rawResponse);
  console.log(returnValue);
}

const it = main();
it.next(); // Remember, the first next() call doesn't accept input

這是它的工作原理。首先,我們聲明一個request函數(shù)和main生成器函數(shù)。接下來,通過調(diào)用main()創(chuàng)建一個迭代器it。然后,我們從調(diào)用it.next()開始。  
在第一行的function * main(),在yield request('https://some-url.com')之后執(zhí)行暫停。request()隱式地返回undefined,因此我們實際上生成了undefined值,但這并不重要—我們沒有使用該值。  
當(dāng)request()函數(shù)中的fetch()調(diào)用完成時,it.next(res)將會被調(diào)用并完成下列兩件事:
it繼續(xù)執(zhí)行;和  
it將res傳遞給生成器函數(shù),該函數(shù)被分配給rawResponse  
最后,main()的其余部分將同步完成。  
這是一個非?;A(chǔ)的設(shè)置,應(yīng)該與promise有一些相似之處。有關(guān)yield和異步性的更詳細介紹,請參閱此文。

生成器是一次性

我們不能重復(fù)使用生成器,但可以從生成器函數(shù)創(chuàng)建新的生成器。

function * makeGen() {
  yield 42;
}

const g1 = makeGen();
const g2 = makeGen();
g1.next(); // { value: 42, done: false }
g1.next(); // { value: undefined, done: true }
g1.next(); // No way to reset this!
g2.next(); // { value: 42, done: false }
...
const g3 = makeGen(); // Create a new generator
g3.next(); // { value: 42, done: false }

無限序列

迭代器表示序列,有點像數(shù)組。所以,我們應(yīng)該能夠?qū)⑺械鞅硎緸閿?shù)組,對吧?
然而,并不是的。數(shù)組在創(chuàng)建時需要立即分配,而迭代器是延遲使用的。數(shù)組是迫切需要的,因為創(chuàng)建一個包含n個元素的數(shù)組需要首先創(chuàng)建/計算所有n個元素,以便將它們存儲在數(shù)組中。相反,迭代器是惰性的,因為序列中的下一個值只有在使用時才會創(chuàng)建/計算。  
因此,表示無限序列的數(shù)組在物理上是不可能的(我們需要無限內(nèi)存來存儲無限項?。骺梢暂p松地表示(而不是存儲)該序列。  
讓我們創(chuàng)建一個從1到正無窮數(shù)的無窮序列。與數(shù)組不同,這并不需要無限內(nèi)存,因為序列中的每個值只有在使用時才會懶散地計算出來。

function * makeInfiniteSequence() {
  var curr = 1;
  while (true) {
    yield curr;
    curr += 1;
  }
}

const is = makeInfiniteSequence();
is.next(); { value: 1, done: false }
is.next(); { value: 2, done: false }
is.next(); { value: 3, done: false }
... // It will never end

有趣的事實:這類似于Python生成器表達式vs列表理解。雖然這兩個表達式在功能上是相同的,但是生成器表達式提供了內(nèi)存優(yōu)勢,因為值的計算是延遲的,而列表理解則是立即計算值并創(chuàng)建整個列表。

關(guān)于如何理解JavaScript中的Generators問題的解答就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道了解更多相關(guān)知識。

文章名稱:如何理解JavaScript中的Generators
標(biāo)題鏈接:http://bm7419.com/article2/ijhioc.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供品牌網(wǎng)站制作電子商務(wù)、云服務(wù)器、用戶體驗網(wǎng)站營銷、ChatGPT

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

綿陽服務(wù)器托管