JavaScript 常用特性解释 —— 类型、迭代器、生成器、同步/异步

AnthonyMeroy 发布于4月前 阅读73次
0 条评论

JavaScript 常用特性解释 —— 类型、迭代器、生成器、同步/异步

某些 JavaScript(ECMAScript)特性比其他的容易理解。生成器(Generators)看起来很奇怪——像 C/C++ 中的指针。类型(Symbols)看起来同时既像原语又像对象。

这些特性都是相互关联,相互构建的。因此你不能脱离其他特性而只理解一个。

因此在本文,我会涉及到类型、全局类型、迭代器、可迭代对象、生成器、异步/等待,以及异步迭代器。 首先我会解释“为什么”他们在这里,然后我会用一些有用的例子来展示他们是如何工作的。

这是一个相当高阶的问题,但是它并不复杂。本文会让你牢牢掌握这些所有概念。

好的,我们开始吧。

JavaScript 常用特性解释 —— 类型、迭代器、生成器、同步/异步

Symbols

在 ES2015,一个名为 symbol 的新的(第六类)数据类型产生了。

为什么?

这里列出三个主要原因:

原因 1 ——添加向后兼容的新的内核特性

JavaScript 开发者和 ECMAScript 委员会( TC39 )需要一种可以添加新的对象属性的方式,而不打破已有的方法,比如循环中的 for 或者 Object.keys 。

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

现在如果我添加了另一个属性,也就是在 myObject 添加 newProperty ,如果你运行 Object.keys(myObject) ,那么应该仍然返回之前的值,[firstName, lastName],而不要返回 [firstName, lastName, newProperty] 。如何做到这一点?

早前我们确实不能做到,因此一个名为 Symbols 的新的数据类型产生了。

如果你作为一个 symbol 来添加 newProperty ,然后 Object.keys(myObject) 会无视掉这个属性(由于它不识别它),并仍然返回 [firstName, lastName] !

原因 2 ——避免命名冲突

他们仍然想保留这些属性的唯一性。通过这种方式他们可以保留添加到全局的新属性(而且你可以添加对象属性)而不用担心命名冲突。

例如,你有一个对象,在对象中你正在添加一个自定义的 toUpperCase 到全局的 Array.prototype 。

现在,想想你加载了另一个库(或者说是 ES2019 发布的库),而且它的 Array.prototype.toUpperCase 版本与自定义的不同。然后你的函数可能会由于命名冲突而崩溃。

JavaScript 常用特性解释 —— 类型、迭代器、生成器、同步/异步

那么你要如何解决这种你可能不知道的命名冲突的问题?这就是 Symbols 要出现的地方。他们内部创建了唯一值,可以让你创建添加属性而不用担心命名冲突。

原因 3 ——通过“众所周知(Well-known)” 的 Symbols 允许钩子(hooks)调用到内核方法

想象你希望使用一些内核函数,比如说 String.prototype.search 来调用你的自定义函数。也就是说,  ‘somestring’.search(myObject);  应该调用 myObject 的搜索函数,并将 ‘somestring’ 作为参数传入!怎样才能做到?

这就是 ES2015 提出的一系列全局 symbols ,即被称为“众所周知” 的 symbols 。而且只要你的对象包含这些 symbols 的其中一个作为属性,你就能将内核函数重新定位来调用你的函数!

关于这部分,在此我们不多说,我会在本文后面部分深入讨论。但是首先,我们先了解 Symbols 实际上是怎么工作的。

创建 Symbols

你可以通过调用名为 Symbol 全局的函数/对象创建一个 symbol 。这个函数返回了一个数据类型为 symbol 的值。

JavaScript 常用特性解释 —— 类型、迭代器、生成器、同步/异步

注:因为 Symbol 有方法,它们表面上可能与对象相似,但他们不是——他们是原语。你可以将它们看做一个“特殊”对象,他们与一般的对象有相似之处,但是他们表现的不像对象。

例如:Symbols 和对象一样有方法,但是不同于对象,它们是不可变的且唯一的。

Symbols 不能使用 “new” 关键字来创建

因为 symbols 不是对象,而 new 关键字返回了一个对象,我们不能使用 new 返回一个 symbols 数据类型。

var mySymbol = new Symbol(); //throws error

Symbols 有“描述”

Symbols 可以包含一个描述——就是为了记录日志而使用。

//mySymbol variable now holds a "symbol" unique value
//its description is "some text"
const mySymbol = Symbol('some text');

Symbols 具有唯一性

const mySymbol1 = Symbol('some text');
const mySymbol2 = Symbol('some text');
mySymbol1 == mySymbol2 // false

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

如果不通过 Symbol() 创建 Symbol ,你可以调用 Symbol.for(<key>) 。它需要传一个 “key”(string)来创建一个 Symbol 。如果这个 key 对应的 symbol 已经存在了,就会简单返回之前的 symbol !因此如果我们调用 Symbol.for 方法,它就会表现的像一个单例模式。

var mySymbol1 = Symbol.for('some key'); //creates a new symbol
var mySymbol2 = Symbol.for('some key'); // **returns the same symbol
mySymbol1 == mySymbol2 //true

使用 .for 的实际运用就是在一个地方创建一个 Symbol ,然后在其他地方访问相同的 Symbol 。

警告:Symbol.for 会使 symbol 不具有唯一性,因此如果  key  相同,你最后会重写里面的值。如果可能的话,尽量避免这么做!

Symbol 的“描述” vs. “key”

若只是为了更清楚的说,如果你不使用 Symbol.for ,那么 Symbols 是具有唯一性的。然而,如果你使用了它,而且如果你的 key 不是唯一的,那么返回的 symbols 也不是唯一的。

JavaScript 常用特性解释 —— 类型、迭代器、生成器、同步/异步

Symbols 可以是一个对象属性键

这是 Symbols 的一个非常奇特的事情——而且也是最令人困惑的。尽管他们看起来像一个对象,他们确实是原语。我们可以将 symbol 像 String 一样作为一个属性键关联到一个对象。

事实上,这也是使用 Symbols 的主要方式——作为对象属性!

JavaScript 常用特性解释 —— 类型、迭代器、生成器、同步/异步

注:使用 symbols 的对象属性称为“键属性”。

括号操作符 vs. 点操作符

因为点操作符只能用于字符串属性,在这你不能使用点操作符,因此你应该使用括号操作符。

JavaScript 常用特性解释 —— 类型、迭代器、生成器、同步/异步

使用 Symbols 的三个主要原因——回顾

现在我们回顾一下(上面说到的)三个主要原因来了解 Symbols 是如何工作的。

原因 #1 ——对于循环和其他的方法来说,Symbols 是不可见的

下面例子中的 for-in 循环遍历了对象 obj ,但是不知道(或者忽略了)prop3 和 prop4 ,因为它们是 symbols 。

JavaScript 常用特性解释 —— 类型、迭代器、生成器、同步/异步

下面是另一个例子, Object.keys 和 Object.getOwnPropertyNames 方法忽略了 Symbols 的特性名称。

JavaScript 常用特性解释 —— 类型、迭代器、生成器、同步/异步

原因 #2 ——Symbol 是唯一的

假设你想要一个叫做 Array.prototype.includes 的全局 Array 对象。它将与 JavaScript(ES2018)开箱即用的 includes 方法冲突。你该如何添加它才能不冲突呢?

首先,用 Symbol 创建一个名为 includes 变量,给它分配一个 Symbol 。然后使用括号表示法添加此变量(现在是一个 Symbol )到全局 Array 中。分配任何一个你想要的功能。

最后使用括号表示法调用这个函数。但是请注意,你必须在括号里传递真实的 Symbol 而不是一个字符串,类似于:arr[includes]() 。

JavaScript 常用特性解释 —— 类型、迭代器、生成器、同步/异步

原因 #3 ——众所周知的 Symbols(“全局”Symbols)

默认情况下,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 或字符串,并在发现索引的时候返回索引。

JavaScript 常用特性解释 —— 类型、迭代器、生成器、同步/异步

在 ES2015 中,它首先检测是否在查询 regExp (RegExp对象) 时实现了 Symbol.search 方法。如果是的话,就调用这个函数并将工作交给它。而且像 RegExp 这样的核心对象实现了 Symbol.search 的 Symbol ,确实做了这个工作。

查看原文: JavaScript 常用特性解释 —— 类型、迭代器、生成器、同步/异步

  • greendog
  • purplefish
需要 登录 后回复方可回复, 如果你还没有账号你可以 注册 一个帐号。