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

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

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

这些功能都是相互关联的,而且相互构建。因此若是不理解一件事你就没法理解另外一件事es6

因此在本文中,将介绍symbols,global-symbols,iterators,iterables,generators ,async/await 和async iterators。将首先解释“ 为何 ”,并经过一些有用的例子展现他们是如何工做的。redux

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

好的,让咱们开始吧promise

Symbols

在ES2015中,建立了一个新的(第6个)数据类型symbol浏览器

为何?

三个主要缘由是:bash

缘由1 - 添加具备向后兼容性的新核心功能

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

例如,若是一个对象,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.而后自定义函数可能会因名称冲突而中断。

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

缘由3 - 经过“众所周知的”符号(“Well-known” Symbols)容许钩子调用核心方法

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

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

咱们如今先不谈论这个问题,将在本文后面详细介绍全部细节。但首先,让咱们了解Symbols实际是如何工做的。

建立Symbols

能够经过调用Symbol全局函数/对象来建立符号Symbol 。该函数返回数据类型的值symbol

注意: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也不是惟一的。

Symbols能够是一个对象属性键

这对于Symbols来讲是一个很是独特的东西———— 也是最使人困惑的。虽然它们看起来像一个对象,但它们是原始的。咱们能够将Symbol做为属性键添加到对象,就像String同样。

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

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

[]操做符与.操做符

不能使用.操做符,由于.操做符仅适用于字符串属性,所以应使用[]操做符。

使用Symbol的3个主要缘由 - review

让咱们回顾一下的三个主要缘由来了解Symbol是如何工做的。

缘由1 - Symbols对于循环和其余方法是不可见的

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

下面是另外一个示例,其中 Object.keysObject.getOwnPropertyNames忽略了Symbol的属性名称。

缘由2 - Symbols是惟一的

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

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

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

缘由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或字符串,并返回索引(若是找到)。

在ES2015中,它首先检查是否在查询regExp(RegExp对象)中实现了Symbol.search方法。若是实现了,那么它调用该函数并将工做委托给它。像RegExp这样的核心对象实现了实际完成工做的SymbolSymbol.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到咱们的代码。

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

不是必定须要经过RegExp。能够传递任何实现Symbol.search并返回任何所需内容的自定义对象。

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

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

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方法。

可是,可以在咱们本身的对象中使用这些现有方法不是很好吗?为了实现这一点,咱们须要制定全部开发人员能够遵循的规则,并使其对象与现有方法一块儿使用。

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

规则是:

  1. 主对象/类应该存储一些数据。
  2. 主对象/类必须具备全局“众所周知的”Symbolssymbol.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对象和迭代:

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

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

Generator 函数

为何?

主要有两个缘由:

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

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

缘由1 - 迭代的包装器

不是经过遵循全部这些规则来使咱们的类/对象成为一个iterable,咱们能够简单地建立一个“Generator”方法来简化这件事情。

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

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

例1:

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

在类中使用generator

例2:

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

直接使用Generators做为函数
重要说明:虽然在上面的例子中,使用 “iterator”这个词来表示 allUsers ,但它确实是一个 generator对象。

generator对象具备方法throw和方法return以外的next方法,可是出于实际目的,咱们能够将返回的对象用做“迭代器”。

缘由2 - 提供更好和更新的控制流程

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

请注意,与普通函数不一样,generator函数能够yield(存储函数statereturn值)并准备好在其产生的点处获取其余输入值。

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

正常函数与generator函数

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

generator控制流程

generator语法和用法

generator功能能够经过如下方式使用:

咱们能够在“yield”以后得到更多代码(与“return”语句不一样)

就像return关键字同样,yield关键字也会返回值 - 但它容许咱们在yielding以后拥有代码

能够有多个yield

能够有多个yield语句

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

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

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

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

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

经过 next从外部将值传回generator

generator帮助消除“回调地狱”

若是有多个异步调用,会进入回调地狱。

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

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

制流程像 “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更优雅和简单。

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工做原理以及如何使用它。
    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功能,可帮助循环异步函数数组,以得到每一个异步函数的结果,就像在普通循环中同样。

进一步阅读

ECMAScript 2015+

  1. 如下是ECMAScript 2016,2017和2018中全部新功能的示例
  2. 查看这些有用的ECMAScript 2015(ES6)提示和技巧
  3. 5个在ES6中修复的JavaScript“坏”部分
  4. ES6中的“类”是新的“坏”部分吗?
相关文章
相关标签/搜索