【ArchSummit架构师峰会】探讨数据与人工智能相互驱动的关系>>> 了解详情
写点什么

深入浅出 ES6(八):Symbols

  • 2015-08-24
  • 本文字数:4782 字

    阅读完需:约 16 分钟

编者按:ECMAScript 6 已经正式发布了,作为它最重要的方言,Javascript 也即将迎来语法上的重大变革,InfoQ 特开设“深入浅出ES6 ”专栏,来看一下ES6 将给我们带来哪些新内容。本专栏文章来自 Mozilla Web 开发者博客,由作者授权翻译并发布。

你是否知道 ES6 中的 Symbols 是什么,它有什么作用呢?我相信你很可能不知道,那就让我们一探究竟!

Symbols 并非用来指代某种 Logo。

它们也不是可以用作代码的小图标。

它们不是代替其它东西的文学手法。

它们更不可能被用来指代谐音词 Cymbals(铙钹)。

(编程的时候最好不要演奏铙钹,它们太过吵闹,很可能导致你的程序崩溃。)

那么,Symbols 到底是什么呢?

它是 JavaScript 的第七种原始类型

1997 年 JavaScript 首次被标准化,那时只有六种原始类型,在 ES6 以前,JS 程序中使用的每一个值都是以下几种类型之一:

  • Undefined 未定义
  • Null 空值
  • Boolean 布尔类型
  • Number 数字类型
  • String 字符串类型
  • Object 对象类型

每种类型都是多个值的集合,前五个集合是有限的。布尔类型只有两个值,truefalse,不会再创造第三种布尔值;数字类型和字符串类型的值更多,标准指明一共有 18,437,736,874,454,810,627 种不同的数字(包括NaN, 亦即“Not a Number”的缩写,代表非数字),可能存在的字符串类型的值拥有无以匹敌的数量,我估算了一下大约是 (2144,115,188,075,855,872 − 1) ÷ 65,535 种……当然,我很可能得出了一个错误的答案,但字符串类型值的集合一定是有限的。

然而,对象类型值的集合是无限的。每一个对象都像珍贵的雪花一样独一无二,每一次你打开一个 Web 页面,都会创建一堆对象。

ES6 新特性中的 symbol 也是值,但它不是字符串,也不是对象,而是是全新的——第七种类型的原始值。

让我们一起探讨一下 symbol 的实际应用场景。

从一个简单的布尔类型出发

有时候你可以非常轻松地将别人的外部数据存储到一个 JavaScript 对象中。

举 个例子,假设你正在写一个 JS 库,可以通过 CSS transitions 使 DOM 元素在屏幕上移动。你可能会注意到,当你尝试在一个 div 元素上同时应用多重 CSS transitions 时并不会生效。实际效果是丑陋而又不连续的“跳闪”。你认为可以修复这个问题,但前提是你需要一种发现给定元素是否已经移动过的方 法。

应当如何解决这个问题呢?

一种方法是,用 CSS API 来告诉浏览器元素是否正在移动,但这样简直小题大做。在元素移动的第一时间内你的库就应该记录下移动的状态,所以它自然知道元素正在移动。

你真正想要的是一种持续跟踪某个元素正在移动的方法。你可以维护一个数组,记录所有正在移动的元素,每当你的库被调用来移动某个元素时,你可以检索数组来查看元素是否已经存在,亦即它是否正在移动中。

当然,如果数组非常大的话,线性搜索将会非常缓慢。

实际上你只想为元素设置一个标记:

复制代码
if (element.isMoving) {
smoothAnimations(element);
}
element.isMoving = true;

这样也会有一些潜在的问题,事实上,你的代码很可能不是唯一一段操作 DOM 的代码。

  1. 你创建的属性很可能影响到其它使用了for-inObject.keys()的代码。
  2. 一些聪明的库作者可能已经考虑并使用了这项技术,这样一来你的库就会与已有的库产生某些冲突
  3. 当然,很可能你比他们更聪明,你先采用了这项技术,但是他们的库仍然无法与你的库默契配合。
  4. 标准委员会可能决定为所有的元素增加一个.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;

这只会造成无畏的眼疲劳。

借助于密码学,你可以生成一个唯一的属性名称:

复制代码
// 获取 1024 个 Unicode 字符的无意义命名
var isMoving = SecureRandom.generateName();
...
if (element[isMoving]) {
smoothAnimations(element);
}
element[isMoving] = true;

object[name]语法允许你使用几乎任何字符串作为属性名称。所以这个方法行之有效:冲突几乎是不可能的,并且你的代码看起来也很简洁。

但是这也将带来不良的调试体验。每当你在控制台输出(console.log())包含那个属性的元素时,你将会看到一堆巨大的字符串垃圾。假使你需要比这多得多的类似属性呢?你如何保持它们整齐划一?每当你重载的时候它们的命名甚至都不一样!

为什么这个问题如此困难?我们只想要一个小小的布尔值啊!

symbol 是最终的解决方案

symbol 是程序创建并且可以用作属性键的值,并且它能避免命名冲突的风险。

var mySymbol = Symbol();调用Symbol()创建一个新的 symbol,它的值与其它任何值皆不相等。

字符串或数字可以作为属性的键,symbol 也可以,它不等同于任何字符串,因而这个以 symbol 为键的属性可以保证不与任何其它属性产生冲突。

复制代码
obj[mySymbol] = "ok!"; // 保证不会冲突
console.log(obj[mySymbol]); // ok!

想要在上述讨论的场景中使用 symbol,你可以这样做:

复制代码
// 创建一个独一无二的 symbol
var isMoving = Symbol("isMoving");
...
if (element[isMoving]) {
smoothAnimations(element);
}
element[isMoving] = true;

有关这段代码的一些解释:

  • Symbol("isMoving")中的isMoving被称作描述。你可以通过console.log()将它打印出来,对调试非常有帮助;你也可以用.toString()方法将它转换为字符串呈现;它也可以被用在错误信息中。
  • element[isMoving]被称作一个 _ 以 symbol 为键(symbol-keyed)的属性 _。简而言之,它的名字是symbol而不是一个字符串。除此之外,它与一个普通的属性没有什么区别。
  • 以 symbol 为键的属性属性与数组元素类似,不能被类似obj.name的点号法访问,你必须使用方括号访问这些属性。
  • 如果你已经得到了 symbol,那么访问一个以 symbol 为键的属性同样简单,以上的示例很好地展示了如何获取element[isMoving]的值以及如何为它赋值。如果我们需要,可以查看属性是否存在:if (isMoving in element),也可以删除属性:delete element[isMoving]
  • 另一方面,只有当isMoving在当前作用域中时才会生效。这是 symbol 的弱封装机制:模块创建了几个 symbol,可以在任意对象上使用,无须担心与其它代码创建的属性产生冲突。

symbol 键的设计初衷是避免初衷,因此 JavaScript 中最常见的对象检查的特性会忽略 symbol 键。例如,for-in循环只会遍历对象的字符串键,symbol 键直接跳过,Object.keys(obj)Object.getOwnPropertyNames(obj)也是一样。但是 symbols 也不完全是私有的:用新的 API Object.getOwnPropertySymbols(obj)就可以列出对象的 symbol 键。另一个新的 API,Reflect.ownKeys(obj),会同时返回字符串键和 symbol 键。(我们将在随后的文章中讲解 Reflect(反射) API)。

慢慢地我们会发现,越来越多的库和框架将大量使用 symbol,语言本身也会将 symbol 应用于广泛的用途。

但是,到底什么是 symbol 呢?

复制代码
> typeof Symbol()
"symbol"

确切地说,symbol 与其它类型并不完全相像。

symbol 被创建后就不可变更,你不能为它设置属性(在严格模式下尝试设置属性会得到 TypeError 的错误)。他们可以用作属性名称,这些性质与字符串类似。

另一方面,每一个 symbol 都独一无二,不与其它 symbol 等同,即使二者有相同的描述也不相等;你可以轻松地创建一个新的 symbol。这些性质与对象类似。

ES6 中的 symbol 与 Lisp 和 Ruby 这些语言中更传统的 symbol 类似,但不像它们集成得那么紧密。在 Lisp 中,所有的标识符都是 symbol;在 JS 中,标识符和大多数的属性键仍然是字符串,symbol 只是一个额外的选项。

关于 symbol 的忠告:symbol 不能被自动转换为字符串,这和语言中的其它类型不同。尝试拼接 symbol 与字符串将得到 TypeError 错误。

复制代码
> 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

通过String(sym)sym.toString()可以显示地将 symbol 转换为一个字符串,从而回避这个问题。

获取 symbol 的三种方法

有三种获取 symbol 的方法。

  • 调用 Symbol()。正如我们上文中所讨论的,这种方式每次调用都会返回一个新的唯一 symbol。
  • 调用 Symbol.for(string)。这种方式会访问 symbol 注册表,其中存储了已经存在的一系列 symbol。这种方式与通过Symbol()定义的独立 symbol 不同,symbol 注册表中的 symbol 是共享的。如果你连续三十次调用Symbol.for("cat"),每次都会返回相同的 symbol。注册表非常有用,在多个 web 页面或同一个 web 页面的多个模块中经常需要共享一个 symbol。
  • 使用标准定义的 symbol,例如:Symbol.iterator。标准根据一些特殊用途定义了少许的几个 symbol。

如果你尚不确定 symbol 是否实用,最后这一章将向你展示 symbol 在实际应用中发挥的巨大作用,非常有趣!

symbol 在 ES6 规范中的应用

在之前的文章《深入浅出ES6(二):迭代器和for-of 循环》中,我们已经领略了借助ES6 symbol 的力量避免代码冲突的方法,循环 for (var item of myArray)首先调用myArray[Symbol.iterator](),当时我提到这种写法是为了替代myArray.iterator(),拥有更好的向后兼容性。

现在我们知道 symbol 到底是什么了,自然很容易理解为什么我们要创造一个 symbol 以及它为我们带来什么新特性。

ES6 中还有其它几处使用了 symbol 的地方。(这些特性在 Firefox 里尚未实现。)

  • 使 instanceof 可扩展。在 ES6 中,表达式object instanceof constructor被指定为构造函数的一个方法:constructor[Symbol.hasInstance](object)。这意味着它是可扩展的。
  • 消除新特性和旧代码之间的冲突。这一点非常复杂,但是我们发现,添加某些 ES6 数组方法会破坏现有的 Web 网站。其它 Web 标准有相同的问题:向浏览器中添加新方法会破坏原有的网站。然而,破坏问题主要由动态作用域引起,所以 ES6 引入一个特殊的 symbol——Symbol.unscopables,Web 标准可以用这个 symbol 来阻止某些方法别加入到动态作用域中。
  • 支持新的字符串匹配类型。在 ES5 中,str.match(myObject)会尝试将myObject转换为正则表达式对象(RegExp)。在 ES6 中,它会首先检查myObject是否有一个myObject[Symbol.match](str)方法。现在的库可以提供自定义的字符串解析类,所有支持RegExp对象的环境都可以正常运行。

这些用例的应用范围都非常小,很难看到这些特性通过它们自身影响我们每日的代码,长期来看才能体现它们的价值。实际上,symbol 是 PHP 和 Python 中的__doubleUnderscores在 JavaScript 语言环境中的改进版。标准将借助 symbol 的力量在未来向语言中添加新的钩子,同时无风险地将新特性添加到你已有的代码中。

我何时可以使用 ES6 symbol?

symbol 在 Firefox 36 和 Chrome 38 中均已被实现。Firefox 中的实现由我亲自完成,所以如果你的 symbol 像铙钹(cymbals)一样行为异常,请直接联系我!

为了支持那些尚未支持原生 ES6 symbol 的浏览器,你可以使用一个 polyfill,例如 core.js 。因为 symbol 与其它类型不尽相同,所以 polyfill 目前不是很完美。请阅读注意事项

下一篇文章,我们将奉上一篇 Gastón Silva 的文章,讲解如何使用 Babel 和 Broccoli 来接触更多的 ES6 新特性,借鉴这篇文章的经验你可以轻松地开始 ES6 之旅。

接 下来,我们将深入浅出 Collections,这个特性被期待已久,最终在 ES6 版本加入到 JavaScript 中。我们将回溯到编程的起源,探索两个古 老的特性,紧接着讨论两个非常相似的特性,它们的生命周期短,但是威力巨大。所以请记得回来,一起探索接下来的旅程!到时见!

2015-08-24 03:0026220
用户头像

发布了 63 篇内容, 共 131.7 次阅读, 收获喜欢 38 次。

关注

评论

发布
暂无评论
发现更多内容

Python 教程之数据分析(6)—— 数据分析的数学运算

海拥(haiyong.site)

Python 9月月更

字节跳动 DanceCC 工具链系列之Xcode LLDB耗时监控统计方案

字节跳动终端技术

ios xcode swift LLVM 客户端

华为云WeLink助力平房区打造智慧政务办公

科技怪咖

校招前端面试题

夏天的味道123

JavaScript 前端

LED显示屏是否可以实现智能化控制

Dylan

LED显示屏 户外LED显示屏 led显示屏厂家

【荣耀开发者服务平台—百亿曝光扶持等你来】智慧服务快应用卡片接入指南(上)

荣耀开发者服务平台

JavaScript 前端 UI 安卓 honor

【中秋福利】大数据告诉你:今年中秋礼品这样选

前嗅大数据

大数据 数据分析 数据采集 中秋 互联网+

设计模式的艺术 第六章抽象工厂设计模式练习(开发一款新的手机游戏软件,该软件能够支持IOS和Android等多个智能手机操作系统平台。针对不同手机操作系统,该游戏软件提供了不同的游戏操作控制类和游戏界面控制类,并提供相应的工厂类来封装这些类的初始化过程)

代廉洁

设计模式的艺术

iOS端如何实现微信分享链接与登陆

MobTech袤博科技

微信 iOS SDK

博云 Kubernetes 开源榜单贡献度进入全球前十

BoCloud博云

云计算 开源 云原生

什么是数据湖?全面解读数据湖与数据仓库的区别

雨果

数据中台 数据仓库 数据湖 DaaS数据即服务

NFT艺术品交易平台:有哪些功能?

开源直播系统源码

NFT 数字藏品 数字藏品软件

高并发下的网络 IO 模型设计

C++后台开发

后台开发 reactor 高并发 epoll 网络io模型

Dubbo Mesh - 从服务框架到统一服务控制平台

阿里巴巴云原生

阿里云 开源 微服务 云原生 dubbo

博弈论(depu)与孙子兵法-02(46/100)

hackstoic

博弈论

离谱了!京东T7手写「并发编程知识手册」,从原理到项目实战详解

了不起的程序猿

Java 并发编程 java程序员 java面试 java编程

“数智化”时代 ,房企转型路径与挑战的一种技术思路

Speedoooo

小程序 前端开发 数字化转型 移动开发 小程序容器

直播预告 | PolarDB-X 动手实践系列——PolarDB-X 数据导入导出功能

阿里云数据库开源

MySQL 数据库 阿里云 云原生 PolarDB-X

Python 教程之数据分析(5)—— 使用 Python 进行数据分析和可视化 | 第 2 套

海拥(haiyong.site)

Python 9月月更

大众CEO迪斯提前卸任,成败皆因软件

雨果

软件定义汽车

字节前端必会面试题

helloworld1024fd

JavaScript

leetcode 101. Symmetric Tree 对称二叉树(简单)

okokabcd

LeetCode 算法与数据结构

2022年全年Java岗面试题总结+一线互联网大厂Java岗面经/面试题总结!

程序员小毕

Java 程序员 面试 程序人生 后端

中国IPv6“高速公路”,全面建成 IANA被管理权限移交 ,IP地址管理何去何从

郑州埃文科技

ipv6 ipv4 IANA

如何在 Jenkins CI/CD 流水线中保护密钥?

SEAL安全

DevOps jenkins CI/CD 密钥管理 CI/CD管道

源码 | SpringBoot启动流程大揭秘

六月的雨在InfoQ

源码 springboot SpringBoot实战 9月月更 SpringBoot启动流程

一加现在属于OPPO吗 资深“加油”来解答

Geek_8a195c

javaweb

喜羊羊

javaWeb 9月月更

实战Elasticsearch6的join类型

程序员欣宸

elasticsearch 9月月更

广东省湛江市等保测评机构有几家?怎么做?

行云管家

等保 等级保护 等保测评 湛江

测试平台解决了什么问题?

老张

测试平台

深入浅出ES6(八):Symbols_JavaScript_Jason Orendorff_InfoQ精选文章