本文中,我将介绍symbols,global symbols,iterators,iterables,generators ,async/await 和async iterators。我将首先解释“ 为什么 ”他们在那里,并展示他们如何使用一些有用的例子。
符号
在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的新数据类型被创建了。
如果你使用Symbols添加newProperty,那么Object.keys(myObject)会忽略它(因为它不知道它),仍然返回[firstName, lastName] !
原因#2 - 避免名称冲突
他们还希望保持这些属性的独特性。通过这种方式,他们可以继续向全局添加新属性(并且可以添加对象属性),而无需担心名称冲突。
例如,假设您有一个对象,您要将自定义toUpperCase添加到全局Array.prototype中。
现在,假设您加载了另一个库(或ES2019出来)并且它的Array.prototype.toUpperCase有不同的版本.然后您的函数可能会因名称冲突而中断
Array.prototype.toUpperCase = function () {
var i;
for (i = 0; i < this.length; i++) {
this[i] = this[i].toUpperCase()
}
return this
}
var myArray = ['abc', 'bcd']
myArray.toUpperCase()
那你怎么解决这个未知情况下的名称冲突?这就是它的Symbols用武之地。它们在内部创建了独特的值,允许您创建添加属性而不必担心名称冲突。
理由#3 - 通过全局变量Symbols的钩子能启用核心方法
假设您需要一些核心功能,比如说String.prototype.search调用自定义功能。
也就是:‘somestring’.search(myObject);
应该调用myObject’s search 方法并将 ‘somestring’ 作为参数传递!我们怎么做?
在ES2015提出了一称为“well-known” symbols的全局 symbols。只要您的对象将其中一个symbols 作为属性,您就可以重定向核心函数来调用您的函数!
我们现在不能谈论这个问题,本文将在后面详细介绍所有细节。现在,让我们了解符号实际上是如何工作的。
创建符号
您可以通过调用名为的全局函数/对象来创建符号Symbol 。该函数返回数据类型的值symbol。
var mySymbols= symbols()
注意:Symbols看起来可能像对象,因为它们有方法,但它们不是 - 它们是原始的。您可以将它们视为与常规对象具有某些相似性的“特殊”对象,但它们的行为与常规对象不同。
Symbols具有与对象类似的方法,但与对象不同,它们是不可变的且唯一的。
不能通过“new”关键字创建Symbols
因为Symbols不是对象而new关键字会返回对象
var mySymbol = new Symbol(); //抛出错误
Symbols 可以有描述为 - 它只是用于记录目的。
// mySymbol变量现在包含一个“符号”唯一值
//它的描述是“some text”
const mySymbol = Symbol('some text');
Symbol 是独一无二的
const mySymbol1 = Symbol('some text');
const mySymbol2 = Symbol('some text');
mySymbol1 == mySymbol2 // false
如果我们使用“Symbol.for”方法,Symbol的行为就像一个单例
您可以通过Symbol.for()代替Symbol()创建Symbol。 只需要输入一个“key”(字符串)来创建一个符号。如果一个符号key已经存在,它只返回旧符号!因此,如果我们使用该Symbol.for方法,它就像一个singleton 。
var mySymbol1 = Symbol .for('some key'); //创建一个新符号
var mySymbol2 = Symbol .for('some key'); // ** 返回相同的符号
mySymbol1 == mySymbol2 // true
真正原因是使用 .for是在一个地方创建一个Symbol ,并从其他地方接收了相同的 Symbol 。
用“关键”字Key描述 Symbol,只是为了让她更清楚,如果你不使用Symbol.for ,那么符号是独一无二的。但是,如果您使用它,那么如果您key 不是唯一的,则返回的符号也不是唯一的。
注意: Symbol.for如果键是相同的,你将最终覆盖值,这将使符号非唯一!所以尽可能避免这种情况!
var mySymbol1 = Symbol('some key'); //创建一个新符号
var mySymbol2 = Symbol('some key'); // ** 返回相同的符号
var mySymbol3 = Symbol .for('some key'); //创建一个新符号
var mySymbol4 = Symbol .for('some key'); // ** 返回相同的符号
mySymbol3== mySymbol4 // true
mySymbol1 == mySymbol2 // false
Symbol可以是对象属性键,这也是使用Symbols的主要方式之一 作为对象属性!
const mySymbol = Symbol('some descrition'); //创建一个新符号
const myObject = {name:‘bmw’}
myObject [mySymbol] = 'this is a car'
console.log(myObject [mySymbol] )//this is a car
注意,
Symbol 作为属性名,该属性不会出现在for…in、for…of循环中,也不会被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回。但是,它也不是私有属性,有一个Object.getOwnPropertySymbols方法,可以获取指定对象的所有 Symbol 属性名。
Object.getOwnPropertySymbols方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值。Symbol 值作为对象属性名时,不能用点运算符,Symbol 值必须放在方括号之中。
使用 Symbol 值定义属性时不适用用点运算符,因为点运算符仅适用于字符串属性,因此您应使用括号[]运算符。
使用Symbol 的3个主要原因
原因#1 - 符号对于循环和其他方法是不可见的
下面示例中的for-in循环遍历一个对象,obj但它不知道(或忽略)prop3,prop4因为它们是符号。
const obj = {};
let foo = Symbol("foo");
Object.defineProperty(obj, foo, {
value: "foobar",
});
for (let i in obj) {
console.log(i); // 无输出
}
Object.getOwnPropertyNames(obj)
// []
Object.getOwnPropertySymbols(obj)
// [Symbol(foo)]
上面代码中,使用Object.getOwnPropertyNames方法得不到Symbol属性名,需要使用Object.getOwnPropertySymbols方法。
另一个新的 API,Reflect.ownKeys方法可以返回所有类型的键名,包括常规键名和 Symbol 键名。
let obj = {
[Symbol('my_key')]: 1,
enum: 2,
nonEnum: 3
};
Reflect.ownKeys(obj)
// ["enum", "nonEnum", Symbol(my_key)]
原因#2 - Symbol是唯一的
假设您想要一个Array.prototype.includes在全局Array对象上调用的功能。它将与JavaScript(ES2018)开放对外的默认方法冲突。如何在不碰撞的情况下添加它?
首先,创建一个具有适当名称的变量includes并为其指定一个Symbol。然后将此变量(现在是Symbol)添加到全局Array使用[ ]表示法。分配您想要的任何功能。
最后:使用括号表示法调用该函数,您必须在括号内传递实际Symbol,如:arrincludes而不是字符串。
var includes = Symbol('will store custom includes method')
Array.prototype[includes] = ()=> {
console.log('inside includes fun');
}
var arr = [1,2,3]
console.log(arr.includes(1));
console.log(arr['includes'](1));
console.log(arr[includes]());
理由#3。Well-known Symbols (即, “global” symbols)
默认情况下,JavaScript会自动创建一堆符号变量并将它们分配给全局Symbol对象(是的,Symbol()我们用来创建符号)。
在ECMAScript2015中,Symbols提供了 11 个内置的 Symbol 值,指向语言内部使用的方法。
这些符号的一些例子是:Symbol.match,Symbol.replace,Symbol.search,Symbol.iterator和Symbol.split。
由于这些全局符号是全局的并且是公开的,我们可以使核心方法调用我们的自定义函数而不是内部函数。
一个例子: Symbol.search
对象的Symbol.search属性,指向一个方法,当该对象被String.prototype.search方法调用时,会返回该方法的返回值。
String.prototype.search(regexp)
// 等同于
regexp[Symbol.search](this)
class MySearch {
constructor(value) {
this.value = value;
}
[Symbol.search](string) {
return string.indexOf(this.value);
}
}
'foobar'.search(new MySearch('foo')) // 0
'ragrao'.search(/rao/)4
'ragrao'.search('rao')4
Symbol.search(DEFAULT BEHAVIOR)的内部工作原理
解析 ‘rajarao’.search(‘rao’);
将“rajarao”转换为String对象 new String(“rajarao”)
将“rao”转换为RegExp对象 new Regexp(“rao”)
调用search“rajarao”字符串对象的方法。
search方法内部调用Symbol.search作用于“rao”对象上(将搜索委托回“rao”对象)并传递“rajarao”。像这样的东西:"rao"Symbol.search
"rao"Symbol.search返回指数的结果4来search发挥作用,最后,search返回4到我们的代码。
迭代器和Iterables
在一些示例中,我们不能使用for-of循环或spread运算符来从Users类中提取数据。我们必须使用自定义get方法。
class Users{
constructor(user){
this.users = users
}
get(){
return this.users
}
}
const allUsers = new Users([{name:'raja'},{name:'john'},{name:'matt'}])
for (const user of Users){
console.log(item)//TypeError Users is no iterable
}
[...allUsers]// TypeError Users is no iterable
遵循以下这6个规则,则主要对象被称为“ 可迭代 ”。
- 主对象/类应该存储一些数据。
- 主对象/类必须具有global “well-known” symbol,symbol.iterator作为其属性,该symbol根据规则3至6实现特定方法。
- 此symbol.iterator方法必须返回另一个对象 - “迭代器”对象。
- 这个“迭代器”对象必须有一个称为next方法的方法。
- 该next方法应该可以访问存储在规则1中的数据。
- 如果我们调用iteratorObj.next(),如果它想要返回更多值或者它不想再返回任何数据,它应该将规则#1中的一些存储数据作为{value:,done:false},或者作为{done:true} 格式返回
Generator 函数
Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。
语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。
执行 上Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。
形式上,Generator 函数是一个普通函数,但是有两个特征。
- 一是,function关键字与函数名之间有一个星号;
- 二是,函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)。
Generator 功能主要有两个: - 为迭代提供更高级别的抽象
- 提供更新的控制流来帮助解决诸如“回调地狱”之类的问题。
#1 - 迭代的包装器
关于Generator 的一些要点:
Generator方法*在类中有一个新语法,而Generator函数有语法function * myGenerator(){}。
调用生成器myGenerator()返回一个generator也实现iterator协议(规则)的对象,因此我们可以将其用作iterator对外接口用的返回值。
生成器使用特殊yield语句来返回数据。
yield 语句跟踪以前的呼叫,并从它停止的地方继续。
如果你yield在循环中使用它,它只会在每次我们next()在迭代器上调用方法时执行一次。
例1:
下面的代码向您展示了如何使用生成器方法(*getIterator())而不是使用该Symbol.iterator方法并实现next遵循所有规则的方法。
class Users{
constructor(users){
this.users = users
this.len = users.length
}
*getInterator(){
for (let i in this.users){
yield this.users[i]
}
}
}
const allUsers = new Users([{name:'raja'},{name:'john'},{name:'matt'}])
const allUsersIterator = allUsers.getIterator()
console.log(allUsersIterator.next());
console.log(allUsersIterator.next());
console.log(allUsersIterator.next());
console.log(allUsersIterator.next());
for (const u of allUsersIterator){
console.log(u.name)
}
console.log([...allUsersIterator]);
例2:
您可以进一步简化它。使函数成为生成器(带*语法),并使用一次yield返回一个值,如下所示。
function*Users(users){
for (let i in users){
yield users[i++]
}
}
const allUsers = new Users([{name:'raja'},{name:'john'},{name:'matt'}])
console.log(allUsers.next());
console.log(allUsers.next());
console.log(allUsers.next());
console.log(allUsers.next());
for (const u of allUsersIterator){
console.log(u.name)
}
console.log([...allUsers]);
原因#2 - 提供更好和更新的控制流程
帮助提供新的控制流程,帮助我们以新的方式编写程序并解决诸如“回调地狱”之类的问题。
请注意,与普通函数不同,生成器函数可以yield(存储函数state和return值)并准备好在其产生的点处获取其他输入值。
写到这里才发现篇是鸡肋