[译]JavaScript Symbols, Iterators, Generators, Async/Await, and Async Iterators

栏目: JavaScript · 发布时间: 6年前

内容简介:原文地址:某些JavaScript(ECMAScript)功能比其他功能更容易理解。所以在本文中,将介绍

原文地址: 简单解释JavaScript Symbols, Iterators, Generators, Async/Await, and Async Iterators

某些JavaScript(ECMAScript)功能比其他功能更容易理解。 Generators 看起来很奇怪-- 就像 C/C ++ 中的指针一样。 Symbols 看起来像原始值和对象。

这些功能都是相互关联的,并且相互构建。所以如果不理解一件事你就无法理解另一件事。

所以在本文中,将介绍 symbols,global-symbols,iterators,iterables,generators ,async/await 和async iterators 。将首先解释“ 为什么 ”,并通过一些有用的例子展示他们是如何工作的。

这是一个相对高阶的主题,但它并不复杂。本文应该让你很好地掌握所有这些概念。

好的,让我们开始吧

Symbols

在ES2015中,创建了一个新的(第6个)数据类型 symbol

为什么?

三个主要原因是:

原因1 - 添加具有向后兼容性的新核心功能

JavaScript开发人员和ECMAScript委员会(TC39)需要一种方法来添加新的对象属性,而不会破坏现有方法像 for...in 循环或JavaScript方法像 Object.keys

例如,如果一个对象, var myObject = {firstName:'raja', lastName:'rao'} 运行 Object.keys(myObject) 它将返回 [firstName, lastName]

现在,如果我们添加另一个属性,为 myObject 设置 newProperty ,如果运行 Object.keys(myObject) 它应该 仍然 返回旧值(即,以某种方式使之忽略新加入的 newproperty ),并且只显示 [firstName, lastName] 而不是 [firstName, lastName, newProperty] 。这该如何做?

我们之前无法真正做到这一点,因此创建了一个名为 Symbols 的新数据类型。

如果作为symbol添加 newProperty ,那么 Object.keys(myObject) 会忽略它(因为它不识别它),仍然返回 [firstName, lastName]

原因2 - 避免名称冲突

他们还希望保持这些属性的独特性。通过这种方式,可以继续向全局添加新属性(并且可以添加对象属性),而无需担心名称冲突。

例如,假设有一个自定义的对象,将在对象中将自定义 toUpperCase 函数添加到全局 Array.prototype

现在,假设加载了另一个库(或着ES2019发布的库)并且它有与自定义函数不同的 Array.prototype.toUpperCase .然后自定义函数可能会因名称冲突而中断。

[译]JavaScript Symbols, Iterators, Generators, Async/Await, and Async Iterators

那怎么解决这个可能不知道的名称冲突?这就是 Symbols 用武之地。它们在内部创建了独特的值,允许创建添加属性而不必担心名称冲突。

原因3 - 通过“众所周知的”符号(“Well-known” Symbols)允许钩子调用核心方法

假设需要一些核心方法,比如 String.prototype.search 调用自定义函数。也就是说, ‘somestring’.search(myObject) ;应该调用 myObject 的搜索函数并将 ‘somestring’ 作为参数传递,我们该怎么做?

这就是ES2015提出了一系列称为“众所周知”的Symbols的全局Symbols。只要你的对象将其中一个Symbols作为属性,就可以重新定位核心函数来调用你的函数。

我们现在先不谈论这个问题,将在本文后面详细介绍所有细节。但首先,让我们了解Symbols实际是如何工作的。

创建Symbols

可以通过调用 Symbol 全局函数/对象来创建符号 Symbol 。该函数返回数据类型的值 symbol

[译]JavaScript Symbols, Iterators, Generators, Async/Await, and Async Iterators
注意

:Symbols可能看起来像对象,因为它们有方法,但它们不是 - 它们是原始的。可以将它们视为与常规对象具有某些相似性的“特殊”对象,但它们的行为与常规对象不同。

例如:Symbols具有与对象类似的方法,但与对象不同,它们是不可变的且唯一的。

“new”关键字无法创建Symbols

因为Symbols不是对象而new关键字应该返回Object,所以我们不能通过new来返回symbols 数据类型。

var mySymbol = new Symbol(); //抛出错误

Symbols有“描述”

Symbols可以有描述 - 它只是用于记录目的。

// mySymbol变量现在包含一个“Symbols”唯一值
//它的描述是“some text” 
const mySymbol = Symbol('some text');
复制代码

Symbols是唯一的

const mySymbol1 =Symbols('some text'); 
const mySymbol2 =Symbols('some text'); 
mySymbol1 == mySymbol2 // false
复制代码

如果我们使用“Symbol.for”方法,Symbols表现的就像一个单例

如果不通过 Symbol() 创建Symbol,可以通过 Symbol.for(<key>) 创建 symbol , 。这需要一个 “key” (字符串)来创建一个Symbol。如果一个 key 对应的Symbol已经存在,它只返回旧Symbol。因此,如果我们使用该 Symbol.for 方法,它就像一个单例。

var mySymbol1 = Symbol .for('some key'); //创建一个新symbol
var mySymbol2 = Symbol .for('some key'); // ** 返回相同的symbol
 mySymbol1 == mySymbol2 // true
复制代码

使用 .for 真正原因是在一个地方创建一个Symbols,并从其他地方访问相同的Symbols。

注意: Symbol.for 如果键是相同的,将覆盖之前的值,这将使Symbol非唯一,所以尽可能避免这种情况。

Symbols的key与描述

若只是为了让事情更清楚,如果不使用 Symbol.for ,那么Symbol是唯一的。但是,如果使用 Symbol.for ,而且 key 不是唯一的,则返回的Symbol也不是唯一的。

[译]JavaScript Symbols, Iterators, Generators, Async/Await, and Async Iterators

Symbols可以是一个对象属性键

这对于Symbols来说是一个非常独特的东西———— 也是最令人困惑的。虽然它们看起来像一个对象,但它们是原始的。我们可以将Symbol作为属性键添加到对象,就像 String 一样。

实际上,这是使用 Symbols 的主要方式之一 ,作为对象属性。

[译]JavaScript Symbols, Iterators, Generators, Async/Await, and Async Iterators
注意

:使用Symbols的对象属性是“键属性”。

[]操作符与.操作符

不能使用.操作符,因为.操作符仅适用于字符串属性,因此应使用[]操作符。

[译]JavaScript Symbols, Iterators, Generators, Async/Await, and Async Iterators

使用Symbol的3个主要原因 - review

让我们回顾一下的三个主要原因来了解Symbol是如何工作的。

原因1 - Symbols对于循环和其他方法是不可见的

下面示例中使用 for-in 循环遍历一个对象 obj ,但它不知道(或忽略) prop3,prop4 因为它们是symbols。

[译]JavaScript Symbols, Iterators, Generators, Async/Await, and Async Iterators
下面是另一个示例,其中 Object.keysObject.getOwnPropertyNames

忽略了Symbol的属性名称。

[译]JavaScript Symbols, Iterators, Generators, Async/Await, and Async Iterators

原因2 - Symbols是唯一的

假设想要在全局 Array 对象上调用 Array.prototype.includes 的功能。它将与JavaScript(ES2018)默认方法 includes 冲突。如何在不冲突的情况下添加它?

首先,创建一个具有合适名称的变量 includes 并为其指定一个symbol。然后将此变量(现在是symbol)添加到全局 Array 使用括号表示法。分配想要的任何功能。

最后使用括号表示法调用该函数。但请注意,必须在括号内传递实际symbol,如: arr[includes]() 而不是字符串。

[译]JavaScript Symbols, Iterators, Generators, Async/Await, and Async Iterators

原因3-众所周知的Symbols(即“全局”symbols)

默认情况下,JavaScript会自动创建一堆Symbols变量并将它们分配给全局 Symbol 对象(使用相同的Symbol()去创建Symbols)。

ECMAScript 2015,这些Symbols被加入到核心对象如数组和字符串的核心方法如 String.prototype.searchString.prototype.replace

这些symbols的一些例子是: Symbol.match,Symbol.replace,Symbol.search,Symbol.iterator和Symbol.split

由于这些全局Symbols是全局的并且是公开的,我们可以使用核心方法调用自定义函数而不是内部函数。

一个例子: Symbol.search

例如, String 对象的 String.prototype.search 公共方法搜索 regExp 或字符串,并返回索引(如果找到)。

[译]JavaScript Symbols, Iterators, Generators, Async/Await, and Async Iterators

在ES2015中,它首先检查是否在查询 regExp (RegExp对象)中实现了 Symbol.search 方法。如果实现了,那么它调用该函数并将工作委托给它。像 RegExp 这样的核心对象实现了实际完成工作的Symbol Symbol.search

Symbol.search的内部工作原理

  1. 解析 ‘rajarao’.search(‘rao’);
  2. “rajarao” 转换为String对象 new String(“rajarao”)
  3. “rao” 转换为RegExp对象 new Regexp(“rao”)
  4. 调用字符串对象 “rajarao” 的方法 search ,传递 'rao' 对象为参数。
  5. search 方法调用“rao”对象内部方法 Symbol.search (将搜索委托返回“rao”对象)并传递 “rajarao” 。像这样: "rao"[Symbol.search]("rajarao")
  6. "rao"[Symbol.search]("rajarao") 返回索引结果4传递给 search 函数,最后, search 返回4到我们的代码。

下面的伪代码片段显示了代码内部的工作方式:

[译]JavaScript Symbols, Iterators, Generators, Async/Await, and Async Iterators

不是一定需要通过RegExp。可以传递任何实现Symbol.search并返回任何所需内容的自定义对象。

自定义String.search方法来调用自定义函数

下面的例子展示了我们如何使 String.prototype.search 调用自定义Product类的搜索功能 - 多亏了 Symbol.search 全局 Symbol

[译]JavaScript Symbols, Iterators, Generators, Async/Await, and Async Iterators

Symbol.search(CUSTOM BEHAVIOR)的内部工作原理

  1. 解析 ‘barsoap’.search(soapObj) ;
  2. “barsoap” 转换为String对象 new String(“barsoap”)
  3. 由于 soapObj 已经是对象,不要进行任何转换
  4. 调用“barsoap”字符串对象的 search 方法。
  5. search 方法调用“soapObj”对象内部方法 Symbol.search (它将搜索委托回“soapObj”对象)并传递 “barsoap” 作为参数。像这样: soapObj[Symbol.search]("barsoap")
  6. soapObj[Symbol.search]("barsoap") 返回索引结果 FOUNDsearch 函数,最后, search 返回 FOUND 到我们的代码。

好的,让我们转到Iterators。

迭代器和Iterables

为什么?

在几乎所有的应用程序中,我们都在不断处理数据列表,我们需要在浏览器或移动应用程序中显示这些数据。通常我们编写自己的方法来存储和提取数据。

但问题是,我们已经有了 for-of 循环和扩展运算符 (…) 等标准方法来从标准对象(如数组,字符串和映射)中提取数据集合。为什么我们不能将这些标准方法用于我们的Object?

在下面的示例中,我们不能使用 for-of 循环或 (…) 运算符来从 Users 类中提取数据。我们必须使用自定义 get 方法。

[译]JavaScript Symbols, Iterators, Generators, Async/Await, and Async Iterators

但是,能够在我们自己的对象中使用这些现有方法不是很好吗?为了实现这一点,我们需要制定所有开发人员可以遵循的规则,并使其对象与现有方法一起使用。

如果他们遵循这些规则从对象中提取数据,那么这些对象称为“迭代”。

规则是:

  1. 主对象/类应该存储一些数据。
  2. 主对象/类必须具有全局“众所周知的”Symbols symbol.iterator 作为其属性,Symbols根据规则#3至#6实现特定方法。
  3. symbol.iterator 方法必须返回另一个对象 - “迭代器”对象。
  4. 这个“迭代器”对象必须有一个称为 next 的方法。
  5. next 方法应该可以访问存储在规则1中的数据。
  6. 如果我们调用 iteratorObj.next() ,它应该返回规则#1中的一些存储数据无论是想要返回更多值 {value:<stored data>, done: false} ,还是不想返回任何数据 {done: true}

如果遵循所有这6个规则,则来自规则#1的主要对象被称为 可迭代 。 它返回的对象称为 迭代器

我们来看看如何创建 Users 对象和迭代:

[译]JavaScript Symbols, Iterators, Generators, Async/Await, and Async Iterators

重要说明:如果我们传递一个 iterable(allUsers)for-of 循环或扩展运算符,将会在内部调用 <iterable>[Symbol.iterator]() 获取迭代器(如 allUsersIterator ),然后使用迭代器提取数据。

所以在某种程度上,所有这些规则都有一个返回 iterator 对象的标准方法。

Generator 函数

为什么?

主要有两个原因:

  1. 为迭代提供更高级别的抽象
  2. 提供更新的控制流来帮助解决诸如“回调地狱”之类的问题。

我们来看看它的详细内容。

原因1 - 迭代的包装器

不是通过遵循所有这些规则来使我们的类/对象成为一个 iterable ,我们可以简单地创建一个“Generator”方法来简化这件事情。

以下是关于Generator的一些要点:

  1. Generator 方法在内部有一个 *<myGenerator> 新语法, Generator 函数有语法 function * myGenerator(){}
  2. 调用generator myGenerator() 返回一个实现iterator协议(规则)的 generator 对象,因此我们可以将其用作 iterator 开箱即用的返回值。
  3. generator使用特殊yield语句来返回数据。
  4. yield 语句保持以前的调用状态,并从它停止的地方继续。
  5. 如果yield在循环中使用它,它只会在每次我们在调迭代器上调用next()方法时执行一次。

例1:

下面的代码展示了如何使用generator方法 (*getIterator()) 实现遵循所有规则的 next 的方法,而不是使用 Symbol.iterator 方法。

[译]JavaScript Symbols, Iterators, Generators, Async/Await, and Async Iterators
在类中使用generator

例2:

可以进一步简化它。使函数成为generator(带*语法),并使用一次 yield 返回一个值,如下所示。

[译]JavaScript Symbols, Iterators, Generators, Async/Await, and Async Iterators
直接使用Generators作为函数
重要说明 :虽然在上面的例子中,使用 “iterator” 这个词来表示 allUsers ,但它确实是一个 generator

对象。

generator对象具有方法 throw 和方法 return 之外的 next 方法,但是出于实际目的,我们可以将返回的对象用作“迭代器”。

原因2 - 提供更好和更新的控制流程

帮助提供新的控制流程,帮助我们以新的方式编写程序并解决诸如“回调地狱”之类的问题。

请注意,与普通函数不同,generator函数可以 yield (存储函数 statereturn 值)并准备好在其产生的点处获取其他输入值。

在下面的图片中,每次看到yield它都可以返回值。可以使用 generator.next(“some new value”) 在它产生的位置使用并传递新值。

[译]JavaScript Symbols, Iterators, Generators, Async/Await, and Async Iterators
正常函数与generator函数

以下示例更具体地说明了控制流如何工作:

[译]JavaScript Symbols, Iterators, Generators, Async/Await, and Async Iterators
generator控制流程

generator语法和用法

generator功能可以通过以下方式使用:

[译]JavaScript Symbols, Iterators, Generators, Async/Await, and Async Iterators

我们可以在“yield”之后获得更多代码(与“return”语句不同)

就像 return 关键字一样, yield 关键字也会返回值 - 但它允许我们在yielding之后拥有代码

[译]JavaScript Symbols, Iterators, Generators, Async/Await, and Async Iterators

可以有多个 yield

[译]JavaScript Symbols, Iterators, Generators, Async/Await, and Async Iterators
可以有多个yield语句

通过 next 方法向generators来回发送值

迭代器 next 方法还可以将值传递回generator,如下所示。

事实上,这个功能使generator能够消除“回调地狱”。稍后将了解更多相关信息。

此功能也在redux-saga等库中大量使用。

在下面的示例中,我们使用空 next() 调用来调用迭代器。然后,当我们第二次调用时传递 23 作为参数 next(23)

[译]JavaScript Symbols, Iterators, Generators, Async/Await, and Async Iterators
通过 next 从外部将值传回generator

generator帮助消除“回调地狱”

如果有多个异步调用,会进入回调地狱。

下面的示例显示了诸如 “co” 之类的库如何使用generator功能,该功能允许我们通过该 next 方法传递值以帮助我们同步编写异步代码。

注意 co 函数如何通过 next(result) 步骤5和步骤10 将结果从 promise 发送回generator。

[译]JavaScript Symbols, Iterators, Generators, Async/Await, and Async Iterators
制流程像 “co” 这样使用 “next(<someval>)”lib 的逐步解释

好的,让我们继续 async / await

异步/ AWAIT

为什么?

正如之前看到的, Generators 可以帮助消除“回调地狱”,但需要一些第三方库 co 来实现这一点。但是“回调地狱”是一个很大的问题,ECMAScript委员会决定为 Generator 创建一个包装器并推出新的关键字 async/await

GeneratorsAsync / Await 之间的区别是:

  1. async / await使用 await 而不是 yield
  2. await 仅适用于 Promises
  3. Async / Await 使用 async function 关键字,而不是 function*

所以 async/await 基本上是Generators的一个子集,并且有一个新的语法糖。

async 关键字告诉JavaScript编译器以不同方式处理该函数。只要到达 await 函数中的关键字,编译器就会暂停。它假定表达式 await 返回一个 promise 并等待,直到 promise 被解决或拒绝,然后才进一步移动。

在下面的示例中,getAmount函数正在调用两个异步函数 getUsergetBankBalance 。我们可以在promise中做到这一点,但使用 async await 更优雅和简单。

[译]JavaScript Symbols, Iterators, Generators, Async/Await, and Async Iterators

ASYNC ITERATORS

为什么?

这是一个非常常见的场景,我们需要在循环中调用异步函数。因此,在ES2018(已完成的提案)中,TC39委员会提出了一个新的Symbol Symbol.asyncIterator 和一个新的构造, for-await-of 以帮助我们轻松地循环异步函数。

常规Iterator对象和异步迭代器之间的主要区别如下:

Iterator对象

  1. Iterator对象的 next() 方法返回值如 {value: ‘some val’, done: false}
  2. 用法: iterator.next() //{value: ‘some val’, done: false}

Async Iterator对象

  1. Async Iterator对象的 next() 方法返回一个Promise,后来解析成类似的 {value: ‘some val’, done: false}
  2. 用法: iterator.next().then(({ value, done })=> {//{value: ‘some val’, done: false}} 以下示例显示了 for-await-of 工作原理以及如何使用它。
    [译]JavaScript Symbols, Iterators, Generators, Async/Await, and Async Iterators
    for-await-of(ES2018)

总结

Symbol- 提供全局唯一的数据类型。主要使用它们作为对象属性来添加新行为,因此不会破坏像Object.keys和for-in循环这样的标准方法。

众所周知的Symbols- 由JavaScript自动生成的Symbols,可用于在我们的自定义对象中实现核心方法

Iterables- 是存储数据集合并遵循特定规则的任何对象,以便我们可以使用标准for-of循环和...扩展运算符从中提取数据。

Iterators- 由Iterables返回并具有next方法它实际上是从Iterables中提取数据。

Generator-为Iterables提供更高级别的抽象。它们还提供了新的控制流,可以解决诸如回调地狱之类的问题,并为诸如此类的事物提供构建块Async/Await。

Async/Await- 为generator提供更高级别的抽象,以便专门解决回调地狱问题。

Async迭代器- 一种全新的2018功能,可帮助循环异步函数数组,以获得每个异步函数的结果,就像在普通循环中一样。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

算法竞赛入门经典(第2版)

算法竞赛入门经典(第2版)

刘汝佳 / 清华大学出版社 / 2014-6-1 / CNY 49.80

《算法竞赛入门经典(第2版)》是一本算法竞赛的入门与提高教材,把C/C++语言、算法和解题有机地结合在一起,淡化理论,注重学习方法和实践技巧。全书内容分为12 章,包括程序设计入门、循环结构程序设计、数组和字符串、函数和递归、C++与STL入门、数据结构基础、暴力求解法、高效算法设计、动态规划初步、数学概念与方法、图论模型与算法、高级专题等内容,覆盖了算法竞赛入门和提高所需的主要知识点,并含有大量......一起来看看 《算法竞赛入门经典(第2版)》 这本书的介绍吧!

SHA 加密
SHA 加密

SHA 加密工具

html转js在线工具
html转js在线工具

html转js在线工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具