详解ES6 Symbol 的用途


Posted in Javascript onOctober 14, 2018

Symbol 唯一的用途就是标识对象属性,表明对象支持的功能。 相比于字符属性名,Symbol 的区别在于唯一,可避免名字冲突。 这样 Symbol 就给出了唯一标识类型信息的一种方式,从这个角度看有点类似 C++ 的 Traits。

解决了什么问题

在 JavaScript 中要判断一个对象支持的功能,常常需要做一些 Duck Test。 比如经常需要判断一个对象是否可以按照数组的方式去迭代,这类对象称为 Array-like。 lodash 中是这样判断的:

function isArrayLike(value) {
  return value != null && isLength(value.length) && !isFunction(value);
}

在 ES6 中提出一个 @@iterator 方法,所有支持迭代的对象(比如 Array、Map、Set)都要实现。 @@iterator 方法的属性键为 Symbol.iterator 而非字符串。 这样只要对象定义有 Symbol.iterator 属性就可以用 for ... of 进行迭代。 比如:

if (Symbol.iterator in arr) {
  for(let n of arr) console.log(n)
}

其他用例

上述例子中 Symbol 标识了这个对象是可迭代的(Iterables),是一个典型的 Symbol 用例。 详情可以参考 ES6 迭代器 一文。 此外利用 Symbol 还可以做很多其他事情,例如:

常量枚举

JavaScript 没有枚举类型,常量概念也通常用字符串或数字表示。例如:

const COLOR_GREEN = 1
const COLOR_RED = 2

function isSafe(trafficLight) {
  if (trafficLight === COLOR_RED) return false
  if (trafficLight === COLOR_GREEN) return true
  throw new Error(`invalid trafficLight: ${trafficLight}`)
}
  • 我们需要认真地排列这些常量的值。如果不小心有两个值重复会很难调试,就像 #define false true 引起的问题一样。
  • 取值可能重复。如果有另一处定义了 BUSY = 1 并不小心把 BUSY 传入,干脆 isSafe(1),理想的枚举概念应该抛出异常,但上述代码无法检测。

Symbol 给出了解决方案:

const COLOR_GREEN = Symbol('green')
const COLOR_RED = Symbol('red')

即使字符串写错或重复也不重要,因为每次调用 Symbol() 都会给出独一无二的值。 这样就可以确保所有 isSafe() 调用都传入这两个 Symbol 之一。

私有属性

由于没有访问限制,JavaScript 曾经有一个惯例:私有属性以下划线起始来命名。 这样不仅无法隐藏这些名字,而且会搞坏代码风格。 可以利用 Symbol 来隐藏这些私有属性:

let speak = Symbol('speak')
class Person {
  [speak]() {
    console.log('harttle')
  }
}

如下几种访问都获取不到 speak 属性:

let p = new Person()

Object.keys(p)           // []
Object.getOwnPropertyNames(p)    // []
for(let key in p) console.log(key) // <empty>

但 Symbol 只能隐藏这些函数,并不能阻止未授权访问。 仍然可以通过 Object.getOwnPerpertySymbols(), Reflect.ownKeys(p) 来枚举到 speak 属性。

新的基本类型

Symbol 是新的基本类型,从此 JavaScript 有 7 种类型:

  • Number
  • Boolean
  • String
  • undefined
  • null
  • Symbol
  • Object

转换为字符串

Symbol 支持 symbol.toString() 方法以及 String(symbol), 但不能通过 + 转换为字符串,也不能直接用于模板字符串输出。 后两种情况都会产生 TypeError,是为了避免把它当做字符串属性名来使用。

转换为数字

不可转换为数字。Number(symbol) 或四则运算都会产生 TypeError。

转换为布尔

Boolean(symbol) 和取非运算都 OK。这是为了方便判断是否包含属性。

包裹对象

Symbol 是基本类型,但不能用 new Symbol(sym) 来包裹成对象,需要使用 Object(sym)。 除了判等不成立外,包裹对象的使用与原基本类型几乎相同:

let sym = Symbol('author')
let obj = {
  [sym]: 'harttle'
}
let wrapped = Object(sym)
wrapped instanceof Symbol  // true,真的是true!!!
obj[sym]          // 'harttle'
obj[wrapped]        // 'harttle'

常见的 Symbol

文章最前面的例子提到的 Symbol.iterator 是一个内置 Symbol。除此之外常见的内置 Symbol 还有:

Symbol.match

Symbol.match 在 String.prototype.match() 中用于获取 RegExp 对象的匹配方法。 我们来改写一下 Symbol.match 标识的方法,

观察 String.prototype.match() 的表现, 下面的例子来自 MDN:

// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/@@match
class RegExp1 extends RegExp {
 [Symbol.match](str) {
  var result = RegExp.prototype[Symbol.match].call(this, str);
  return result ? 'VALID' : 'INVALID';
 }
}

console.log('2012-07-02'.match(new RegExp1('([0-9]+)-([0-9]+)-([0-9]+)')));
// expected output: "VALID"
Symbol.toPrimitive

在对象进行运算时经常会变成 "[object Object]", 这是对象转换为字符串(基本数据类型)的默认行为,定义在 Object.prototype.toString。 比如这个对象:

var count = {
  value: 3
};
count + 2   // "[object Object]2"

这个对象也在表示一个数字,怎么让它可以参加四则运算呢? 给它加一个 Symbol.toPrimitive 属性,来改变它转换为基本类型的行为:

count[Symbol.toPrimitive] = function () {
  return this.value
};
count + 2   // 5

更多内置 Symbol 请参考 MDN 文档: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol#Well-known_symbols

跨 Realm 使用

JavaScript Realm 是指当前代码片段运行的上下文,包括全局变量,比如 Array, Date 这些全局函数。 在打开新标签页、 加载 iframe 或加载 Worker 进程时,都会产生多个 JavaScript Realm。 跨 Realm 通信时这些全局变量是不同的,例如从 iframe 中传递给数组 arr 给父窗口, 父窗口中收到的 arr instanceof Array 为 false,因为它的原型是 iframe 中的那个 Array。

但是一个对象在 iframe 中可以迭代(Iterable),那么在父窗口中也应当能被迭代。 这就要求 Symbol 可以跨 Realm,当然 Symbol.iterator 可以。 如果你定义的 Symbol 也需要跨 Realm,请使用 Symbol Registry API:

// 在 Symbol Registry 中注册一个跨 Realm Symbol
let sym = Symbol.for('foo')
// 获取 Symbol 的键值字符串
Symbol.keyFor(sym)   // 'foo'

内置的跨 Realm Symbol 其实不在 Symbol Registry 中:

Symbol.keyFor(Symbol.iterator)  // undefined

总结

以上所述是小编给大家介绍的ES6 Symbol 的用途,希望对大家有所帮助,如果大家有任何疑问欢迎给我留言,小编会及时回复大家的!

Javascript 相关文章推荐
js异或加解密效果代码
Jun 25 Javascript
javascript cookie操作类的实现代码小结附使用方法
Jun 02 Javascript
基于BootStrap Metronic开发框架经验小结【四】Bootstrap图标的提取和利用
May 12 Javascript
15个非常实用的JavaScript代码片段
Dec 18 Javascript
JS轮播图中缓动函数的封装
Nov 25 Javascript
vue 做移动端微信公众号采坑经验记录
Apr 26 Javascript
vue配置多页面的实现方法
May 22 Javascript
详解Vue一个案例引发「内容分发slot」的最全总结
Dec 02 Javascript
vue和better-scroll实现列表左右联动效果详解
Apr 29 Javascript
Vue 自定义指令实现一键 Copy功能
Sep 16 Javascript
vue项目中在可编辑div光标位置插入内容的实现代码
Jan 07 Javascript
vue element-ui中table合计指定列求和实例
Nov 02 Javascript
javascript实现文本框标签验证的实例代码
Oct 14 #Javascript
Vue+webpack项目配置便于维护的目录结构教程详解
Oct 14 #Javascript
单页面vue引入百度统计的使用方法示例详解
Oct 13 #Javascript
详解解决Vue相同路由参数不同不会刷新的问题
Oct 12 #Javascript
详解webpack loader和plugin编写
Oct 12 #Javascript
深入理解Angularjs 脏值检测
Oct 12 #Javascript
vue中render函数的使用详解
Oct 12 #Javascript
You might like
实用PHP会员权限控制实现原理分析
2011/05/29 PHP
PHP 文本文章分页代码 按标记或长度(不涉及数据库)
2012/06/07 PHP
PHP中使用hidef扩展代替define提高性能
2015/04/09 PHP
PHP性能分析工具xhprof的安装使用与注意事项
2017/12/19 PHP
详解laravel安装使用Passport(Api认证)
2018/07/27 PHP
PHP的mysqli_select_db()函数讲解
2019/01/23 PHP
javaScript - 如何引入js代码
2021/03/09 Javascript
在IE下获取object(ActiveX)的Param的代码
2009/09/15 Javascript
javascript getElementsByClassName 和js取地址栏参数
2010/01/02 Javascript
PHP7新特性简述
2017/06/11 Javascript
js事件委托和事件代理案例分享
2017/07/25 Javascript
vue.js使用v-model指令实现的数据双向绑定功能示例
2018/05/22 Javascript
gulp构建小程序的方法步骤
2019/05/31 Javascript
详解vue中的父子传值双向绑定及数据更新问题
2019/06/13 Javascript
详解微信小程序图片地扯转base64解决方案
2019/08/18 Javascript
js对象简介与基本用法示例
2020/03/13 Javascript
javascript设计模式 ? 状态模式原理与用法实例分析
2020/04/22 Javascript
[01:00:04]DOTA2上海特级锦标赛B组小组赛#1 Alliance VS Spirit第二局
2016/02/26 DOTA
python中base64加密解密方法实例分析
2015/05/16 Python
python筛选出两个文件中重复行的方法
2018/05/31 Python
使用python对excle和json互相转换的示例
2018/10/23 Python
解决python字典对值(值为列表)赋值出现重复的问题
2019/01/20 Python
python多线程同步实例教程
2019/08/11 Python
python 实现保存最新的三份文件,其余的都删掉
2019/12/22 Python
python系统指定文件的查找只输出目录下所有文件及文件夹
2020/01/19 Python
python except异常处理之后不退出,解决异常继续执行的实现
2020/04/25 Python
一张图片能隐含千言万语之隐藏你的程序代码
2012/12/13 HTML / CSS
HTML5给汉字加拼音收起展开组件的实现代码
2020/04/08 HTML / CSS
北美三大旅游网站之一:Travelocity加拿大
2016/08/20 全球购物
日本著名的服饰鞋帽综合类购物网站:MAGASEEK
2019/01/09 全球购物
2014年创卫实施方案
2014/02/18 职场文书
心理健康活动总结
2014/04/30 职场文书
网球场地租赁协议范本
2014/10/07 职场文书
城管年度个人总结
2015/02/28 职场文书
2015年全国科普日活动总结
2015/03/23 职场文书
检讨书模板大全
2015/05/07 职场文书