搜尋
首頁web前端js教程詳解JavaScript ES6中的Generator_基礎知識

今天討論的新特性讓我非常興奮,因為這個特性是 ES6 中最神奇的特性。

這裡的「神奇」代表什麼呢?對於初學者來說,該特性與以往的 JS 完全不同,甚至有些晦澀難懂。從某種意義上說,它完全改變了這門語言的常見行為,這不是「神奇」是什麼。

不僅如此,此特性還可以簡化程式碼,將複雜的「回呼堆疊」改成直線執行的形式。

我是不是鋪墊的太多了?下面開始深入介紹,你自己去判斷吧。
簡介

什麼是 Generator?

看下面程式碼:

function* quips(name) {
 yield "hello " + name + "!";
 yield "i hope you are enjoying the blog posts";
 if (name.startsWith("X")) {
  yield "it's cool how your name starts with X, " + name;
 }
 yield "see you later!";
}
 
function* quips(name) {
 yield "hello " + name + "!";
 yield "i hope you are enjoying the blog posts";
 if (name.startsWith("X")) {
  yield "it's cool how your name starts with X, " + name;
 }
 yield "see you later!";
}

上面程式碼是模仿Talking cat(當下一個非常流行的應用程式)的一部分,點擊這裡試試,如果你對程式碼感到困惑,那就回到這裡來看下面的解釋。

這看起來很像一個函數,這被稱為 Generator 函數,它與我們常見的函數有很多共同點,但還可以看到下面兩個差異:

    通常的函數以 function 開始,但 Generator 函數以 function* 開始。
    在 Generator 函數內部,yield 是一個關鍵字,和 return 有點像。不同點在於,所有函數(包括 Generator 函數)都只能回傳一次,而在 Generator 函數中可以 yield 任意次。 yield 表達式暫停了 Generator 函數的執行,然後可以從暫停的地方恢復執行。

常見的函數不能暫停執行,而 Generator 函數可以,這就是這兩者最大的差異。
原理

呼叫 quips() 時發生了什麼事?

> var iter = quips("jorendorff");
 [object Generator]
> iter.next()
 { value: "hello jorendorff!", done: false }
> iter.next()
 { value: "i hope you are enjoying the blog posts", done: false }
> iter.next()
 { value: "see you later!", done: false }
> iter.next()
 { value: undefined, done: true }

 
> var iter = quips("jorendorff");
 [object Generator]
> iter.next()
 { value: "hello jorendorff!", done: false }
> iter.next()
 { value: "i hope you are enjoying the blog posts", done: false }
> iter.next()
 { value: "see you later!", done: false }
> iter.next()
 { value: undefined, done: true }

我們對普通函數的行為非常熟悉,函數被呼叫時就立即執行,直到函數返回或拋出一個異常,這是所有 JS 程式設計師的第二天性。

Generator 函數的呼叫方法與普通函數一樣:quips("jorendorff"),但呼叫一個Generator 函數時並沒有立即執行,而是傳回了一個Generator 物件(上面程式碼中的iter),這時函數就立即暫停在函數程式碼的第一行。

每次呼叫 Generator 物件的 .next() 方法時,函數就會開始執行,直到遇到下一個 yield 表達式為止。

這就是為什麼我們每次呼叫 iter.next() 時都會得到一個不同的字串,這些都是在函數內部透過 yield 表達式產生的值。

當執行最後一個 iter.next() 時,就到達了 Generator 函數的末尾,所以傳回結果的 .done屬性值為 true,並且 .value 屬性值為 undefined。

現在,回到 Talking cat 的 DEMO,嘗試在程式碼中加入一些 yield 表達式,看看會發生什麼。

從技術層面上講,每當Generator 函數執行遇到yield 表達式時,函數的棧幀— 本地變量,函數參數,臨時值和當前執行的位置,就從堆疊移除,但是Generator 物件保留了對該堆疊幀的引用,所以下次呼叫.next() 方法時,就可以恢復並繼續執行。

值得提醒的是 Generator 並不是多執行緒。在支援多執行緒的語言中,同一時間可以執行多段程式碼,並伴隨著執行資源的競爭,執行結果的不確定性和較好的效能。而 Generator 函數並不是這樣,當一個 Generator 函數執行時,它與其呼叫者都在同一線程中執行,每次執行順序都是確定的,有序的,並且執行順序不會改變。與執行緒不同,Generator 函數可以在內部的 yield 的標誌點暫停執行。

透過介紹 Generator 函數的暫停、執行和恢復執行,我們知道了什麼是 Generator 函數,那麼現在拋出一個問題:Generator 函數到底有什麼用呢?
迭代器

透過上篇文章,我們知道迭代器並不是ES6 的一個內建的類,而只是作為語言的一個擴展點,你可以透過實作[Symbol.iterator]() 和.next() 方法來定義一個迭代器。

但是,實作一個介面還是需要寫一些程式碼的,下面我們來看看在實際中如何實作一個迭代器,以實作一個range 迭代器為例,該迭代器只是簡單地從一個數累加到另一個數,有點像C 語言中的for (;;) 迴圈。

// This should "ding" three times
for (var value of range(0, 3)) {
 alert("Ding! at floor #" + value);
}
 
// This should "ding" three times
for (var value of range(0, 3)) {
 alert("Ding! at floor #" + value);
}

現在有一個解決方案,就是使用 ES6 的類別。 (如果你對 class 語法還不熟悉,不要緊,我會在將來的文章中介紹。)

class RangeIterator {
 constructor(start, stop) {
  this.value = start;
  this.stop = stop;
 }

 [Symbol.iterator]() { return this; }

 next() {
  var value = this.value;
  if (value < this.stop) {
   this.value++;
   return {done: false, value: value};
  } else {
   return {done: true, value: undefined};
  }
 }
}

// Return a new iterator that counts up from 'start' to 'stop'.
function range(start, stop) {
 return new RangeIterator(start, stop);
}
 
class RangeIterator {
 constructor(start, stop) {
  this.value = start;
  this.stop = stop;
 }
 
 [Symbol.iterator]() { return this; }
 
 next() {
  var value = this.value;
  if (value < this.stop) {
   this.value++;
   return {done: false, value: value};
  } else {
   return {done: true, value: undefined};
  }
 }
}
 
// Return a new iterator that counts up from 'start' to 'stop'.
function range(start, stop) {
 return new RangeIterator(start, stop);
}

查看该 DEMO。

这种实现方式与 Java 和 Swift 的实现方式类似,看上去还不错,但还不能说上面代码就完全正确,代码没有任何 Bug?这很难说。我们看不到任何传统的 for (;;) 循环代码:迭代器的协议迫使我们将循环拆散了。

在这一点上,你也许会对迭代器不那么热衷了,它们使用起来很方便,但是实现起来似乎很难。

我们可以引入一种新的实现方式,以使得实现迭代器更加容易。上面介绍的 Generator 可以用在这里吗?我们来试试:

function* range(start, stop) {
 for (var i = start; i < stop; i++)
  yield i;
}
 
function* range(start, stop) {
 for (var i = start; i < stop; i++)
  yield i;
}

查看该 DEMO

上面这 4 行代码就可以完全替代之前的那个 23 行的实现,替换掉整个 RangeIterator 类,这是因为 Generator 天生就是迭代器,所有的 Generator 都原生实现了 .next() 和 [Symbol.iterator]() 方法。你只需要实现其中的循环逻辑就够了。

不使用 Generator 去实现一个迭代器就像被迫写一个很长很长的邮件一样,本来简单的表达出你的意思就可以了,RangeIterator 的实现是冗长和令人费解的,因为它没有使用循环语法去实现一个循环功能。使用 Generator 才是我们需要掌握的实现方式。

我们可以使用作为迭代器的 Generator 的哪些功能呢?

    使任何对象可遍历 — 编写一个 Genetator 函数去遍历 this,每遍历到一个值就 yield 一下,然后将该 Generator 函数作为要遍历的对象上的 [Symbol.iterator] 方法的实现。
    简化返回数组的函数 — 假如有一个每次调用时都返回一个数组的函数,比如:

// Divide the one-dimensional array 'icons'
// into arrays of length 'rowLength'.
function splitIntoRows(icons, rowLength) {
 var rows = [];
 for (var i = 0; i < icons.length; i += rowLength) {
  rows.push(icons.slice(i, i + rowLength));
 }
 return rows;
}

 
// Divide the one-dimensional array 'icons'
// into arrays of length 'rowLength'.
function splitIntoRows(icons, rowLength) {
 var rows = [];
 for (var i = 0; i < icons.length; i += rowLength) {
  rows.push(icons.slice(i, i + rowLength));
 }
 return rows;
}

使用 Generator 可以简化这类函数:

function* splitIntoRows(icons, rowLength) {
 for (var i = 0; i < icons.length; i += rowLength) {
  yield icons.slice(i, i + rowLength);
 }
}
 
function* splitIntoRows(icons, rowLength) {
 for (var i = 0; i < icons.length; i += rowLength) {
  yield icons.slice(i, i + rowLength);
 }
}

这两者唯一的区别在于,前者在调用时计算出了所有结果并用一个数组返回,后者返回的是一个迭代器,结果是在需要的时候才进行计算,然后一个一个地返回。

    无穷大的结果集 — 我们不能构建一个无穷大的数组,但是我们可以返回一个生成无尽序列的 Generator,并且每个调用者都可以从中获取到任意多个需要的值。
    重构复杂的循环 — 你是否想将一个复杂冗长的函数重构为两个简单的函数?Generator 是你重构工具箱中一把新的瑞士军刀。对于一个复杂的循环,我们可以将生成数据集那部分代码重构为一个 Generator 函数,然后用 for-of 遍历:for (var data of myNewGenerator(args))。
    构建迭代器的工具 — ES6 并没有提供一个可扩展的库,来对数据集进行 filter 和 map等操作,但 Generator 可以用几行代码就实现这类功能。

例如,假设你需要在 Nodelist 上实现与 Array.prototype.filter 同样的功能的方法。小菜一碟的事:

function* filter(test, iterable) {
 for (var item of iterable) {
  if (test(item))
   yield item;
 }
}

 
function* filter(test, iterable) {
 for (var item of iterable) {
  if (test(item))
   yield item;
 }
}

所以,Generator 很实用吧?当然,这是实现自定义迭代器最简单直接的方式,并且,在 ES6 中,迭代器是数据集和循环的新标准。

但,这还不是 Generator 的全部功能。
异步代码

异步 API 通常都需要一个回调函数,这意味着每次你都需要编写一个匿名函数来处理异步结果。如果同时处理三个异步事务,我们看到的是三个缩进层次的代码,而不仅仅是三行代码。

看下面代码:

}).on('close', function () {
 done(undefined, undefined);
}).on('error', function (error) {
 done(error);
});
 
}).on('close', function () {
 done(undefined, undefined);
}).on('error', function (error) {
 done(error);
});

异步 API 通常都有错误处理的约定,不同的 API 有不同的约定。大多数情况下,错误是默认丢弃的,甚至有些将成功也默认丢弃了。

直到现在,这些问题仍是我们处理异步编程必须付出的代价,而且我们也已经接受了异步代码只是看不来不像同步代码那样简单和友好。

Generator 给我们带来了希望,我们可以不再采用上面的方式。

Q.async()是一个将 Generator 和 Promise 结合起来处理异步代码的实验性尝试,让我们的异步代码类似于相应的同步代码。

例如:

// Synchronous code to make some noise.
function makeNoise() {
 shake();
 rattle();
 roll();
}

// Asynchronous code to make some noise.
// Returns a Promise object that becomes resolved
// when we're done making noise.
function makeNoise_async() {
 return Q.async(function* () {
  yield shake_async();
  yield rattle_async();
  yield roll_async();
 });
}
 
// Synchronous code to make some noise.
function makeNoise() {
 shake();
 rattle();
 roll();
}
 
// Asynchronous code to make some noise.
// Returns a Promise object that becomes resolved
// when we're done making noise.
function makeNoise_async() {
 return Q.async(function* () {
  yield shake_async();
  yield rattle_async();
  yield roll_async();
 });
}

最大的差別在於,需要在每個非同步方法呼叫的前面加上 yield 關鍵字。

在 Q.async 中,加入一個 if 語句或 try-catch 異常處理,就和在同步程式碼中的方式一樣,與其他編寫非同步程式碼的方式相比,減少了很多學習成本。

Generator 為我們提供了一個更適合人腦思維方式的非同步程式設計模型。但更好的語法也許更有幫助,在 ES7 中,一個基於 Promise 和 Generator 的非同步處理函數正在規劃中,靈感來自 C# 中類似的特性。
相容性

在伺服器端,現在就可以直接在 io.js 中使用 Generator(或在 NodeJs 中以 --harmony 啟動參數來啟動 Node)。

在瀏覽器端,目前只有 Firefox 27 和 Chrome 39 以上的版本才支援 Generator,如果想直接在 Web 上使用,你可以使用 Babel 或 Google 的 Traceur 將 ES6 程式碼轉換為 Web 友善的 ES5 程式碼。

一些題外話:JS 版本的 Generator 最早是由 Brendan Eich 實現,他借鑒了 Python Generator的實現,該實現的靈感來自 Icon,早在 2006 年的 Firefox 2.0 就吸納了 Generator。但標準化的道路是坎坷的,一路下來,其語法和行為都發生了很多改變,Firefox 和 Chrome 中的 ES6 Generator 是由 Andy Wingo 實現 ,這項工作是由 Bloomberg 贊助的。
yield;

關於 Generator 還有一些未提及的部分,我們還沒有涉及到 .throw() 和 .return() 方法的使用,.next() 方法的可選參數,還有 yield* 語法。但我認為這篇文章已經夠長了,就像 Generator 一樣,我們也暫停一下,另外找個時間再剩下的部分。

我們已經介紹了 ES6 中兩個非常重要的特性,那麼現在可以大膽地說,ES6 將改變我們的生活,看似簡單的特性,卻有極大的用處。

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
JavaScript框架:為現代網絡開發提供動力JavaScript框架:為現代網絡開發提供動力May 02, 2025 am 12:04 AM

JavaScript框架的強大之處在於簡化開發、提升用戶體驗和應用性能。選擇框架時應考慮:1.項目規模和復雜度,2.團隊經驗,3.生態系統和社區支持。

JavaScript,C和瀏覽器之間的關係JavaScript,C和瀏覽器之間的關係May 01, 2025 am 12:06 AM

引言我知道你可能會覺得奇怪,JavaScript、C 和瀏覽器之間到底有什麼關係?它們之間看似毫無關聯,但實際上,它們在現代網絡開發中扮演著非常重要的角色。今天我們就來深入探討一下這三者之間的緊密聯繫。通過這篇文章,你將了解到JavaScript如何在瀏覽器中運行,C 在瀏覽器引擎中的作用,以及它們如何共同推動網頁的渲染和交互。 JavaScript與瀏覽器的關係我們都知道,JavaScript是前端開發的核心語言,它直接在瀏覽器中運行,讓網頁變得生動有趣。你是否曾經想過,為什麼JavaScr

node.js流帶打字稿node.js流帶打字稿Apr 30, 2025 am 08:22 AM

Node.js擅長於高效I/O,這在很大程度上要歸功於流。 流媒體匯總處理數據,避免內存過載 - 大型文件,網絡任務和實時應用程序的理想。將流與打字稿的類型安全結合起來創建POWE

Python vs. JavaScript:性能和效率注意事項Python vs. JavaScript:性能和效率注意事項Apr 30, 2025 am 12:08 AM

Python和JavaScript在性能和效率方面的差異主要體現在:1)Python作為解釋型語言,運行速度較慢,但開發效率高,適合快速原型開發;2)JavaScript在瀏覽器中受限於單線程,但在Node.js中可利用多線程和異步I/O提升性能,兩者在實際項目中各有優勢。

JavaScript的起源:探索其實施語言JavaScript的起源:探索其實施語言Apr 29, 2025 am 12:51 AM

JavaScript起源於1995年,由布蘭登·艾克創造,實現語言為C語言。 1.C語言為JavaScript提供了高性能和系統級編程能力。 2.JavaScript的內存管理和性能優化依賴於C語言。 3.C語言的跨平台特性幫助JavaScript在不同操作系統上高效運行。

幕後:什麼語言能力JavaScript?幕後:什麼語言能力JavaScript?Apr 28, 2025 am 12:01 AM

JavaScript在瀏覽器和Node.js環境中運行,依賴JavaScript引擎解析和執行代碼。 1)解析階段生成抽象語法樹(AST);2)編譯階段將AST轉換為字節碼或機器碼;3)執行階段執行編譯後的代碼。

Python和JavaScript的未來:趨勢和預測Python和JavaScript的未來:趨勢和預測Apr 27, 2025 am 12:21 AM

Python和JavaScript的未來趨勢包括:1.Python將鞏固在科學計算和AI領域的地位,2.JavaScript將推動Web技術發展,3.跨平台開發將成為熱門,4.性能優化將是重點。兩者都將繼續在各自領域擴展應用場景,並在性能上有更多突破。

Python vs. JavaScript:開發環境和工具Python vs. JavaScript:開發環境和工具Apr 26, 2025 am 12:09 AM

Python和JavaScript在開發環境上的選擇都很重要。 1)Python的開發環境包括PyCharm、JupyterNotebook和Anaconda,適合數據科學和快速原型開發。 2)JavaScript的開發環境包括Node.js、VSCode和Webpack,適用於前端和後端開發。根據項目需求選擇合適的工具可以提高開發效率和項目成功率。

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

MantisBT

MantisBT

Mantis是一個易於部署的基於Web的缺陷追蹤工具,用於幫助產品缺陷追蹤。它需要PHP、MySQL和一個Web伺服器。請查看我們的演示和託管服務。

SublimeText3 Linux新版

SublimeText3 Linux新版

SublimeText3 Linux最新版

VSCode Windows 64位元 下載

VSCode Windows 64位元 下載

微軟推出的免費、功能強大的一款IDE編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

MinGW - Minimalist GNU for Windows

MinGW - Minimalist GNU for Windows

這個專案正在遷移到osdn.net/projects/mingw的過程中,你可以繼續在那裡關注我們。 MinGW:GNU編譯器集合(GCC)的本機Windows移植版本,可自由分發的導入函式庫和用於建置本機Windows應用程式的頭檔;包括對MSVC執行時間的擴展,以支援C99功能。 MinGW的所有軟體都可以在64位元Windows平台上運作。