原文地址:简单解释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
在ES2015中,建立了一个新的(第6个)数据类型symbol
。浏览器
三个主要缘由是:bash
JavaScript开发人员和ECMAScript委员会(TC39)须要一种方法来添加新的对象属性,而不会破坏现有方法像for...in
循环或JavaScript方法像Object.keys
。ecmascript
例如,若是一个对象,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]
。
他们还但愿保持这些属性的独特性。经过这种方式,能够继续向全局添加新属性(而且能够添加对象属性),而无需担忧名称冲突。
例如,假设有一个自定义的对象,将在对象中将自定义toUpperCase
函数添加到全局Array.prototype
。
如今,假设加载了另外一个库(或着ES2019发布的库)而且它有与自定义函数不一样的Array.prototype.toUpperCase
.而后自定义函数可能会因名称冲突而中断。
那怎么解决这个可能不知道的名称冲突?这就是Symbols
用武之地。它们在内部建立了独特的值,容许建立添加属性而没必要担忧名称冲突。
假设须要一些核心方法,好比String.prototype.search
调用自定义函数。也就是说,‘somestring’.search(myObject)
;应该调用myObject
的搜索函数并将 ‘somestring’
做为参数传递,咱们该怎么作?
这就是ES2015提出了一系列称为“众所周知”的Symbols的全局Symbols。只要你的对象将其中一个Symbols做为属性,就能够从新定位核心函数来调用你的函数。
咱们如今先不谈论这个问题,将在本文后面详细介绍全部细节。但首先,让咱们了解Symbols实际是如何工做的。
能够经过调用Symbol
全局函数/对象来建立符号Symbol
。该函数返回数据类型的值symbol
。
例如:Symbols具备与对象相似的方法,但与对象不一样,它们是不可变的且惟一的。
由于Symbols不是对象而new关键字应该返回Object,因此咱们不能经过new来返回symbols 数据类型。
var mySymbol = new Symbol(); //抛出错误
Symbols能够有描述 - 它只是用于记录目的。
// mySymbol变量如今包含一个“Symbols”惟一值
//它的描述是“some text”
const mySymbol = Symbol('some text');
复制代码
const mySymbol1 =Symbols('some text');
const mySymbol2 =Symbols('some text');
mySymbol1 == mySymbol2 // false
复制代码
若是不经过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非惟一,因此尽量避免这种状况。
若只是为了让事情更清楚,若是不使用Symbol.for
,那么Symbol是惟一的。可是,若是使用Symbol.for
,并且key
不是惟一的,则返回的Symbol也不是惟一的。
这对于Symbols来讲是一个很是独特的东西———— 也是最使人困惑的。虽然它们看起来像一个对象,但它们是原始的。咱们能够将Symbol做为属性键添加到对象,就像String
同样。
实际上,这是使用Symbols
的主要方式之一 ,做为对象属性。
不能使用.操做符,由于.操做符仅适用于字符串属性,所以应使用[]操做符。
让咱们回顾一下的三个主要缘由来了解Symbol是如何工做的。
下面示例中使用for-in
循环遍历一个对象obj
,但它不知道(或忽略)prop3,prop4
由于它们是symbols。
Object.keys
和
Object.getOwnPropertyNames
忽略了Symbol的属性名称。
假设想要在全局Array
对象上调用Array.prototype.includes
的功能。它将与JavaScript(ES2018)默认方法includes
冲突。如何在不冲突的状况下添加它?
首先,建立一个具备合适名称的变量includes
并为其指定一个symbol。而后将此变量(如今是symbol)添加到全局Array
使用括号表示法。分配想要的任何功能。
最后使用括号表示法调用该函数。但请注意,必须在括号内传递实际symbol,如:arr[includes]()
而不是字符串。
默认状况下,JavaScript会自动建立一堆Symbols变量并将它们分配给全局Symbol
对象(使用相同的Symbol()去建立Symbols)。
ECMAScript 2015,这些Symbols被加入到核心对象如数组和字符串的核心方法如String.prototype.search
与String.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
。
‘rajarao’.search(‘rao’);
“rajarao”
转换为String对象 new String(“rajarao”)
“rao”
转换为RegExp对象 new Regexp(“rao”)
“rajarao”
的方法search
,传递'rao'
对象为参数。search
方法调用“rao”对象内部方法Symbol.search
(将搜索委托返回“rao”对象)并传递“rajarao”
。像这样:"rao"[Symbol.search]("rajarao")
"rao"[Symbol.search]("rajarao")
返回索引结果4传递给search
函数,最后,search
返回4到咱们的代码。下面的伪代码片断显示了代码内部的工做方式:
不是必定须要经过RegExp。能够传递任何实现Symbol.search并返回任何所需内容的自定义对象。
下面的例子展现了咱们如何使String.prototype.search
调用自定义Product类的搜索功能 - 多亏了Symbol.search
全局Symbol
。
‘barsoap’.search(soapObj)
;“barsoap”
转换为String对象new String(“barsoap”)
soapObj
已是对象,不要进行任何转换search
方法。search
方法调用“soapObj”对象内部方法Symbol.search
(它将搜索委托回“soapObj”对象)并传递“barsoap”
做为参数。像这样:soapObj[Symbol.search]("barsoap")
soapObj[Symbol.search]("barsoap")
返回索引结果FOUND
给search
函数,最后,search
返回FOUND
到咱们的代码。好的,让咱们转到Iterators。
在几乎全部的应用程序中,咱们都在不断处理数据列表,咱们须要在浏览器或移动应用程序中显示这些数据。一般咱们编写本身的方法来存储和提取数据。
但问题是,咱们已经有了for-of
循环和扩展运算符(…)
等标准方法来从标准对象(如数组,字符串和映射)中提取数据集合。为何咱们不能将这些标准方法用于咱们的Object?
在下面的示例中,咱们不能使用for-of
循环或(…)
运算符来从Users
类中提取数据。咱们必须使用自定义get
方法。
可是,可以在咱们本身的对象中使用这些现有方法不是很好吗?为了实现这一点,咱们须要制定全部开发人员能够遵循的规则,并使其对象与现有方法一块儿使用。
若是他们遵循这些规则从对象中提取数据,那么这些对象称为“迭代”。
规则是:
symbol.iterator
做为其属性,Symbols根据规则#3至#6实现特定方法。symbol.iterator
方法必须返回另外一个对象 - “迭代器”对象。next
的方法。next
方法应该能够访问存储在规则1中的数据。iteratorObj.next()
,它应该返回规则#1中的一些存储数据不管是想要返回更多值{value:<stored data>, done: false}
,仍是不想返回任何数据{done: true}
。若是遵循全部这6个规则,则来自规则#1的主要对象被称为 可迭代。 它返回的对象称为迭代器。
咱们来看看如何建立Users
对象和迭代:
重要说明:若是咱们传递一个iterable(allUsers)for-of
循环或扩展运算符,将会在内部调用<iterable>[Symbol.iterator]()
获取迭代器(如allUsersIterator
),而后使用迭代器提取数据。
因此在某种程度上,全部这些规则都有一个返回iterator
对象的标准方法。
主要有两个缘由:
咱们来看看它的详细内容。
不是经过遵循全部这些规则来使咱们的类/对象成为一个iterable
,咱们能够简单地建立一个“Generator”方法来简化这件事情。
如下是关于Generator的一些要点:
Generator
方法在内部有一个*<myGenerator>
新语法,Generator
函数有语法function * myGenerator(){}
。myGenerator()
返回一个实现iterator协议(规则)的generator
对象,所以咱们能够将其用做iterator
开箱即用的返回值。下面的代码展现了如何使用generator方法(*getIterator())
实现遵循全部规则的next
的方法,而不是使用Symbol.iterator
方法。
能够进一步简化它。使函数成为generator(带*语法),并使用一次yield
返回一个值,以下所示。
“iterator”
这个词来表示
allUsers
,但它确实是一个
generator
对象。
generator对象具备方法throw
和方法return
以外的next
方法,可是出于实际目的,咱们能够将返回的对象用做“迭代器”。
帮助提供新的控制流程,帮助咱们以新的方式编写程序并解决诸如“回调地狱”之类的问题。
请注意,与普通函数不一样,generator函数能够yield
(存储函数state
和return
值)并准备好在其产生的点处获取其余输入值。
在下面的图片中,每次看到yield它均可以返回值。可使用generator.next(“some new value”)
在它产生的位置使用并传递新值。
如下示例更具体地说明了控制流如何工做:
generator功能能够经过如下方式使用:
就像return
关键字同样,yield
关键字也会返回值 - 但它容许咱们在yielding以后拥有代码
yield
next
方法向generators来回发送值迭代器next
方法还能够将值传递回generator,以下所示。
事实上,这个功能使generator可以消除“回调地狱”。稍后将了解更多相关信息。
此功能也在redux-saga等库中大量使用。
在下面的示例中,咱们使用空next()
调用来调用迭代器。而后,当咱们第二次调用时传递23
做为参数next(23)
。
next
从外部将值传回generator
若是有多个异步调用,会进入回调地狱。
下面的示例显示了诸如“co”
之类的库如何使用generator功能,该功能容许咱们经过该next
方法传递值以帮助咱们同步编写异步代码。
注意co
函数如何经过next(result)
步骤5和步骤10 将结果从promise
发送回generator。
“co”
这样使用
“next(<someval>)”
的
lib
的逐步解释
好的,让咱们继续async / await
。
正如以前看到的,Generators
能够帮助消除“回调地狱”,但须要一些第三方库co
来实现这一点。可是“回调地狱”是一个很大的问题,ECMAScript委员会决定为Generator
建立一个包装器并推出新的关键字async/await
。
Generators
和Async / Await
之间的区别是:
await
而不是yield
。await
仅适用于Promises
。Async / Await
使用async function
关键字,而不是function*
。因此async/await
基本上是Generators的一个子集,而且有一个新的语法糖。
async
关键字告诉JavaScript编译器以不一样方式处理该函数。只要到达await
函数中的关键字,编译器就会暂停。它假定表达式await
返回一个promise
并等待,直到promise
被解决或拒绝,而后才进一步移动。
在下面的示例中,getAmount函数正在调用两个异步函数getUser
和getBankBalance
。咱们能够在promise中作到这一点,但使用async await
更优雅和简单。
这是一个很是常见的场景,咱们须要在循环中调用异步函数。所以,在ES2018(已完成的提案)中,TC39委员会提出了一个新的Symbol Symbol.asyncIterator
和一个新的构造,for-await-of
以帮助咱们轻松地循环异步函数。
常规Iterator对象和异步迭代器之间的主要区别以下:
next()
方法返回值如{value: ‘some val’, done: false}
iterator.next() //{value: ‘some val’, done: false}
next()
方法返回一个Promise,后来解析成相似的{value: ‘some val’, done: false}
iterator.next().then(({ value, done })=> {//{value: ‘some val’, done: false}}
如下示例显示了for-await-of
工做原理以及如何使用它。
Symbol - 提供全局惟一的数据类型。主要使用它们做为对象属性来添加新行为,所以不会破坏像Object.keys和for-in循环这样的标准方法。
众所周知的Symbols- 由JavaScript自动生成的Symbols,可用于在咱们的自定义对象中实现核心方法
Iterables- 是存储数据集合并遵循特定规则的任何对象,以便咱们可使用标准for-of循环和...扩展运算符从中提取数据。
Iterators- 由Iterables返回并具备next方法它其实是从Iterables中提取数据。
Generator -为Iterables提供更高级别的抽象。它们还提供了新的控制流,能够解决诸如回调地狱之类的问题,并为诸如此类的事物提供构建块Async/Await。
Async/Await- 为generator提供更高级别的抽象,以便专门解决回调地狱问题。
Async迭代器- 一种全新的2018功能,可帮助循环异步函数数组,以得到每一个异步函数的结果,就像在普通循环中同样。