概述
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 这个属性值 则打印这个属性值的内容,这个属性值就是提供的一个改变函数行为的钩子。