概述
Symbol
类型是一种特殊的、不可变的数据类型,主要用于解决名称冲突的问题,是 es6 引入的第七种原始数据类型,其他六种分别是:null
、 undefined
、 boolean
、 string
、 number
、 object
。
创建 Symbol
Symbol([description])
创建一个全新的Symbol
,description
是对 Symbol 实例的描述,先来看几个示例:
var s0 = Symbol(); |
从上述示例可以看出,即使传递给 Symbol 同样的 “描述” ,得到的 Symbol 也都是独立的。
Symbol.for
在某些场合下可能希望复用之前所创建的 Symbol,使用 Symbol.for
创建的 Symbol 可以做到这一点:
Symbol.for([key])
用 Symbol.for()
方法创建的的 symbol
会被放入一个全局 symbol 注册表中, Symbol.for(key)
方法并不是每次都会创建一个新的 symbol
,它会首先检查给定的 key
是否已经在注册表中了。假如是,则会直接返回上次存储的那个,否则,新建一个与该 key
关联的 symbol
,并放入全局 symbol
注册表中。
var s1 = Symbol.for("foo"); // 创建一个 symbol 并放入 symbol 注册表中, key 为 "foo" |
Symbol.keyFor
Symbol.keyFor(symbol);
该方法用于从 symbol
注册表中获取与某个 symbol
关联的键:
// 创建一个 symbol 并放入 Symbol 注册表,key 为 "foo" |
Symbol 属性名遍历
使用 Symbol 作为对象的属性名是,无论使用 for...in
、for...of
循环或者 Object.keys()
、Object.getOwnPropertyNames()
方法均不会被遍历到,当然有一个Object.getOwnPropertySymbols
方法,可以获取指定对象的所有 Symbol
属性名:
var obj = {}; |
在上面代码中,Object.getOwnPropertySymbols
可以获取 Symbol
键名,但是无法获取到非 Symbol
键名,如果在需要遍历所有键名的场合可以尝试 Reflect.ownKeys
:
var obj = {}; |
Symbol 的内置属性
除了可以定义自己使用的 Symbol 以外,JavaScript 还内建了一些在 ES5 之前没有暴露给开发者的符号,指向内部语言行为。这些符号可以使用以下属性访问:
Symbol.hasInstance
对象的 Symbol.hasInstance
属性指向一个函数,该函数用于判断某对象是否为某构造器的实例,即当调用 instanceof
运算符的时候实际上是调用对象的 Symbol.hasInstance
的属性对应的函数:
class MyArray { |
Symbol.isConcatSpreadable
对象的 Symbol.isConcatSpreadable
属性指向一个布尔值,该函数用于配置某对象作为 Array.prototype.concat()
方法的参数时是否展开其数组元素。
首先对于数组而言,Symbol.isConcatSpreadable
属性默认为 true
, 可以展开:
var a = [1, 2, 3]; |
设置 数组 b
的 Symbol.isConcatSpreadable
值为 false
:
var a = [1, 2, 3]; |
类似数组的对象也可以展开,但它的 Symbol.isConcatSpreadable
属性默认为 false
:
var a = [1, 2, 3]; |
Symbol.iterator
对象的 Symbol.iterator
属性为对象定义了默认的迭代器。该迭代器可以被 for...of
循环结构使用。
var myIterable = {} |
Symbol.match、Symbol.search、Symbol.replace 、 Symbol.split
这四个属性代表的行为类似,这里以 Symbol.match
属性为例说明:
对象的 Symbol.match
属性,指向一个函数。当执行 String.prototype.match()
时,如果该属性存在,会调用它。也就是说 str.match(obj)
等价于调用 obj[Symbol.match](str)
:
var a = {}; |
同样的,执行 str.search(obj)
、 str.replace(obj, replaceStr)
、 str.split(obj, limit)
等价于执行 obj[Symbol.search](str)
、 obj[Symbol.replace](str, replaceStr)
、 obj[Symbol.split](str, limit)
:
var a = { |
Symbol.species
对象的 Symbol.species
属性,指向一个构造函数。创建实例的时候,会使用这个方法的返回值作为构造函数,来创建新的对象
class MyArray1 extends Array { |
在ES5 中 Array.prototype.map
等函数实现类似如下:
Array.prototype.map = function (callback) { |
在ES6 中,Array.prototype.map
等函数实现奖创建对象升级为使用 Symbol.species
属性,其实现类似如下:
Array.prototype.map = function (callback) { |
这种修改可以保证在自定义的数组的子类上执行这些函数,可以让返回的结果的类型能够回归到 Array
类型,否则,直接执行这些函数返回的类型是 MyArray
子类型。数组的 slice
,filter
,splice
,concat
方法都是这样。
Symbol.toPrimitive
对象的 Symbol.toPrimitive
属性,指向一个函数。当此对象被转为原始类型的值时,会调用这个方法。
比如,当执行 +object
的时候,实际上会调用 object[Symbol.toPrimitive]('number')
,当执行 ''+object
,实际上会调用 object[Symbol.toPrimitive]('string')
, 执行 if(object)
实际上会调用 object[Symbol.toPrimitive]('default')
, 通过 Symbol.toPrimitive
可以自由改变对象被类型转换的返回结果:
// 没有 Symbol.toPrimitive 属性的对象 |
注意,hint
的取值只有 number
、 string
和 default
三种,当在进行类型转换的时候,如果 hint === 'number'
分支的返回结果不能转换为数字,则会返回 NAN
。
Symbol.toStringTag
对象的 Symbol.toStringTag
属性,指向一个函数,当对象被调用 Object.prototype.toString
方法的时候,这个函数的返回标签会出现在 toString
方法返回的字符串中:
var obj = {}; |
应用场景
存储对象的基础属性
Symbol
唯一的特性可以保证作为对象的属性名,其属性值不会被误修改:
Symbol('foo') === Symbol('foo'); // false |
由于外界拿不到 foo1
和 foo2
的引用,就可以保证了 它们对应的属性值不会被修改。
但是,这只能防止被误修改,但是实际上还是能被修改的:
var obj = { |
因此,这种存储方式并不是完全私有的,可以存放一些不想被误修改的数据又不介意被修改的数据。
替代标识用的静态变量
比如当记录日志的时候,通常会有多个日志的级别:debug
、 info
、 warn
,以前通常是这么写的:
var levels = { |
这样写也可以工作,但是这些值并不唯一,可能会和其他值冲突。而实际上这些值取什么并无意义,只需要确保不会和其他值冲突即可。因此,完全可以使用 Symbol
替代:
var levels = { |
提供一个修改的钩子
比如之前提到的 Symbol.toStringTag
等就是一个 toString
方法的钩子,使用这个可以改变自定义的对象的显示名称,这在开发工具函数也很有用:
var inspect = Symbol('inspect'); |
如上述例子,编写了一个增强的 log
,默认使用 console.log
直接打印对象,但是如果对象存在 inspect
这个属性值 则打印这个属性值的内容,这个属性值就是提供的一个改变函数行为的钩子。