【探秘ES6】系列专栏(八):JS的第七种基本类型Symbols

ES6做为新一代JavaScript标准,已正式与广大前端开发者见面。为了让你们对ES6的诸多新特性有更深刻的了解,Mozilla Web开发者博客推出了《ES6 In Depth》系列文章。CSDN已获受权,将持续对该系列进行翻译,组织成【探秘ES6】系列专栏,供你们学习借鉴。本文为该系列的第八篇。 
前端

本期咱们要讨论的symbols是个什么东西呢?es6

这里的Symbols不是指的徽标。正则表达式

也不是能在代码中使用的小图片。算法


它也不是表明其它任何东西的一个别名。编程

固然咯,Symbols和Cymbals(铜钹)彻底是两回事。数组


(在编程过程当中使用铜钹可不是一个好主意,吵到你炸!)浏览器

言归正传,什么是Symbols呢?框架

它是Javascript的第七种基本类型dom

自1997年Javascript被标准化以来,它定义了六种基本类型。直到ES6,JS程序中任何一个值都属于如下几种类型之一。ide

  • Undefined

  • Null

  • Boolean

  • Number

  • String

  • Object

每种类型都是一系列值的集。前五个都是有限集。固然,Boolean类型只有true和false两个值,并且他们应该不会给Boolean型增长新值了。其它类型的值基本上都是数字和字符串。理论上说Numbers类型有18,437,736,874,454,810,627个值(包括了NaN,NaN是“Not a Number”的缩写)。String类型中可能的值就太多了,我算算大概有 (2144,115,188,075,855,872 − 1) ÷ 65,535个……固然,我这种算法不必定是精确的。

Object是一个无限集,每个Object都是独一无二的。你随意打开一个Web页面就会生成一大堆新的Object。

ES6 Symbols也是一个集,但它的元素既不是字符串也不是对象。它是ES6的新成员:第七种基本类型。

让咱们来谈谈它的应用场景。

以一个简单的布尔型来举例

在JavaScript中,有时候将一个对象中的数据扩展到其它某个对象中是十分方便的。

例如,假设你正在写一个JS库,目的是使用CSS过渡让DOM元素在屏幕上移动。你应该知道同时使用多个CSS过渡在同一个div上是行不通的。这会引发div不规律跳跃。你打算解决这个问题,不过首先你得想法知道这个元素是否正处在一个过渡中。

怎样来解决这个问题呢?

其中一种方式是使用CSS APIs让浏览器来告诉你元素是否在位移过程当中。但这未免有点杀鸡用牛刀了。你的库应该存储了移动状态:代码中触发过渡的时候就应该记录了!

你真正须要的是一种方法来跟踪记录哪些元素在过渡。你能够把过渡中的元素存在一个数组中。每当你的库触发一个元素的过渡以前,先检测那个元素是否在数组中。

遗憾的是,若是数组很大的话,遍历起来会很耗时。

在你看来最简单的方法实际上是为元素设置一个标识:

if (element.isMoving) {  
  smoothAnimations(element);  
}  
element.isMoving = true;

这样也会有一些潜在的问题。没法避免的事实是代码中会用到这个DOM的地方不止这一处。
  1. 其它代码中若是使用了for-in 或者 Object.keys()会遍历DOM的全部属性(会形成额外性能消耗)。

  2. 一些思惟灵活的库做者会从技术方面考虑——你的库与其它库兼容性会不好。

  3. 一些思惟灵活的库做者也会考虑扩展性——你的库扩展性也会不好。

  4. JS标准委员会未来也许会为全部元素提供一个.isMoving()的方法,那么你须要重构你的代码,那时候你就傻眼了。

固然,你能够用一个冗长或傻瓜式的字符串来做为属性名,只需确保不会和别的属性重名。

if (element.__$jorendorff_animation_library$PLEASE_DO_NOT_USE_THIS_PROPERTY$isMoving__) {  
  smoothAnimations(element);  
}  
element.__$jorendorff_animation_library$PLEASE_DO_NOT_USE_THIS_PROPERTY$isMoving__ = true;

代码写成这样也太虐待本身的眼睛了。

使用加密方法你能够生成一个理论上惟一的属性名:

// get 1024 Unicode characters of gibberish  
var isMoving = SecureRandom.generateName();  
...  
if (element[isMoving]) {  
  smoothAnimations(element);  
}  
element[isMoving] = true;

object[name]语法使你可使用任何字符串做为属性名。因此这样是可行的:不会有命名冲突,看起来还清爽!

可是,这样会致使调试体验糟透了?当你使用console.log()来打印元素的这个属性时,你会看到一大段字符串的垃圾数据。而且,这样的属性不止一个吧?你将如何保持连续性?每次从新加载的时候它们都生成不一样的属性名。

为何要搞得这么复杂?咱们要得仅仅是一个简单的布尔值而已!

Symbols能够解决这个问题

Symbols集中的值能够由程序建立和并做为属性的键来使用,也不用担忧名称冲突。

var mySymbol = Symbol();

调用Symbol()来建立一个新的Symbol值,它不会等同于其余值。

与字符串和数字同样,你可使用symbol来做为属性值。由于它不等同于其它任何字符串,这个symbol-keyed属性能够确保不会与其它任何属性冲突。

obj[mySymbol] = "ok!";  // guaranteed not to collide  
console.log(obj[mySymbol]);  // ok!

接下来这方法就能够解决上面咱们所讨论的那种状况:
// create a unique symbol  
var isMoving = Symbol("isMoving");  
...  
if (element[isMoving]) {  
  smoothAnimations(element);  
}  
element[isMoving] = true;

关于这段代码的几个说明:
  • Symbol(“isMoving”)中的“isMoving”被称做描述。它对调试颇有用。当你使用console.log()就能够打印出对应的symbol值,若是你想把它转换为字符串(好比说在打印错误信息的时候)可使用.toString()。

  • element[isMoving]被称做symbol-keyed属性(使用symbol做为键的属性)。从字面意思就能够说明它就是使用symbol做为属性名而不是使用字符串。除去这一点,它和其它属性并没什么区别。

  • 和数组元素同样,symbol-keyed属性不能经过圆点符号来获取值(obj.name 这样是不行的)。它的值必须经过方括号来获取。

  • 经过symbol的值获取symbol-keyed属性值就很容易了。上面的例子展现了如何获取和设置element[isMoving],咱们能够判断元素的isMoving状态了,若是有必要的话甚至能够删除isMoving状态。

  • 另外一方面,以上的前提是isMoving在当前做用域中。这体现了symbols的弱封装机制:一个模块能够建立几个symbols在对象中任意使用而不用担忧与其它模块的属性冲突。

由于symbol键值是被设计来避免冲突的,因此JavaScript最基本的对象检测特性是会忽略symbol键值的。以for-in循环为例,循环只会遍历对象的字符串类型的键。Symbol键直接被忽略过了。Object.key(obj)和 Object.getOwnPropertyNames(obj) 也是这样运做的。可是sysmbols并不彻底是私有的:可使用新API——Object.getOwnPropertySymbols(obj)将所对象的全部symbol键;另外一个新API—— Reflect.ownKeys(obj),将会同时返回string和symbol类型的键。(在之后的文章中咱们将完整地探讨Reflect API。)

在库和框架中symbols将会有不少用途,不久咱们会看到,JS语言自己对它也会有普遍的使用。

symbols确切定义是什么呢?

<b>> typeof Symbol()  
"symbol"</b>

Symbols和其它基本类型大不同。

从建立开始就是不可变的。你不能为它设置属性(若是你在严谨模式下尝试,会报类型错误)。它能够做为属性名。这是它的类字符串性质。

另外一方面,每个symbol都是惟一的。与其余的不一样(就算他们的描述是同样的)你能够很容易地新建立一个。这是它的类对象性质。

ES6 symbols与Lisp和Ruby中的更传统的symbols很相似,可是没有如此紧密地集成到语言中。在Lisp中,全部的标识符都是symbols。在JS中,标识符和大多数属性的键值的首先还是字符串,Symbols只是为开发人员提供了一个额外选择。

关于symbols的一个忠告:与JS中的其它类型不一样,它不能被自动转换为字符串。试图拼接symbol与字符串将会引发类型错误。

> var sym = Symbol("<3");  
> "your symbol is " + sym  
// TypeError: can't convert symbol to string  
> `your symbol is ${sym}`  
// TypeError: can't convert symbol to string

你能够经过显示地将symbol转换为一个字符串来避免这个问题,经过String(sym)或者sym.toString()。

symbols的三种形式

有三种方法来获取symbol。

  • Call Symbol()。咱们已经讨论过这种方法了,每一次调用它都将返回一个惟一的symbol。

  • Call Symbol.for(string)。这种方法访问一组已经存在的symbol注册表。与经过Symbol()来定一个惟一值不一样的是,symbol注册表中的symbols是共享的。若是你调用Symbol.for(“cat”)三十次,每一次返回都将是同一个symbol。在多页面或者单页面的多模块须要共享symbol时,这是颇有效的方法。

  • 使用标准中定义的Symbol.iterator。标准委员会本身定义了几种symbols。每一种都有它的特殊意义。

若是你仍然不肯定symbols是否对你有帮助,这最后一个章节会颇有趣,由于证明了在实践中symbols是颇有用的。

ES6的文档中对通用symbols的使用是如何介绍的?

咱们已经看过了ES6是如何使用symbol来避免与已有代码命名冲突的。几周前,在关于迭代器的文章中,咱们了解了循环(var item of myArray)是从调用myArray[Symbol.iterator]()开始的。我提到这个方法之前的写法是myArray.iterator(),可是加了symbol之后向后兼容性会更好。

如今咱们知道了symbols的用法和做用。那么就很容易理解为何这样作和这样作的意义是什么。

这里还有其它几个ES6使用通用symbols的场景。(这些特性在Firefox中还没实现。)

  • 使instanceof可扩展。在ES6中,表达式object instanceof constructor被指定为构造函数的一个方法:constructor[Symbol.hasInstance](Object)。这代表它是可扩展的。

  • 消除新特性和旧代码之间的冲突。这比较难理解,但咱们发现一些ES6的数组方法将会破坏旧网站的稳定性。其它的Web标准也会有相似的问题:仅仅是添加新方法到浏览器中,已存在的网站就会受到影响。不管如何,形成这些不稳定性的主要缘由主要是由动态做用域引发的。因此ES6引入了一个特殊的symbol——Symbol.unscopables,这个Web标准能够用来防止某些方法被牵连到动态域中。

  • 支持新的字符串匹配。在ES5中,str.match(myObject)尝试将myObject转换为正则表达式对象。在ES6中,首先检查myObject是否有myObject[Symbol.match](str)方法。如今库就能够给任何有正则表达式对象的地方提供通用的解析类。

所讲到的这几个symbol的应用都不常见,很难看到这些特性自己对咱们的平常代码有任何影响。从长远看就比较有意义了。通用symbols是JavaScript对于PHP和Python中的__doubleUnderscores的改进。标准委员会未来会添加新的hooks到JS中,而不会有影响已有代码的风险。

我何时能够开始使用ES6 symbols?

Firefox 36和Chrome 38已经支持Symbols了。我本身也在Firefox中试过了,若是你运行的时候有问题,你该知道问谁吧——找我!

为了让那些自己还不支持ES6 symbols的浏览器支持它,你可使用pollyfill(一段代码或插件,提供了那些开发者们但愿浏览器原生提供支持的功能),好比core.js。由于Symbols还比较新,因此它的pollyfill还不是那么完善,详细了解请看使用说明。

接下来的两篇博客,首先会讨论一些咱们期待已久的特性终于被ES6支持了,我实在忍不住抱怨它们的姗姗来迟。咱们将从两个很古老的特性做为开始(老到几乎能够追溯到编程历史的起源),紧接着讨论两个与之很是类似的特性,由ephemerons提供技术支持。下次还将深刻讨论collections(集合)。

原文连接:ES6 In Depth: Symbols

本译文遵循Creative Commons Attribution Share-Alike License v3.0

相关文章
相关标签/搜索