JavaScript 中的Symbols, Iterators, Generators, Async/Await, and Async Iterators

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/devincob/article/details/82775088

本文中,我将介绍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个规则,则主要对象被称为“ 可迭代 ”。

  1. 主对象/类应该存储一些数据。
  2. 主对象/类必须具有global “well-known” symbol,symbol.iterator作为其属性,该symbol根据规则3至6实现特定方法。
  3. 此symbol.iterator方法必须返回另一个对象 - “迭代器”对象。
  4. 这个“迭代器”对象必须有一个称为next方法的方法。
  5. 该next方法应该可以访问存储在规则1中的数据。
  6. 如果我们调用iteratorObj.next(),如果它想要返回更多值或者它不想再返回任何数据,它应该将规则#1中的一些存储数据作为{value:,done:false},或者作为{done:true} 格式返回
    在这里插入图片描述

Generator 函数

Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。

语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。

执行 上Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。

形式上,Generator 函数是一个普通函数,但是有两个特征。

  1. 一是,function关键字与函数名之间有一个星号;
  2. 二是,函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)。
    Generator 功能主要有两个:
  3. 为迭代提供更高级别的抽象
  4. 提供更新的控制流来帮助解决诸如“回调地狱”之类的问题。

#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值)并准备好在其产生的点处获取其他输入值。
写到这里才发现篇是鸡肋

猜你喜欢

转载自blog.csdn.net/devincob/article/details/82775088