ES6中的Symbol
文章目录
- Symbol 属性和方法
- Symbol.asyncIterator
- Symbol.iterator
- Symbol.match
- Symbol.replace
- Symbol.search
- Symbol.split
- Symbol.hasInstance
- Symbol.isConcatSpreadable
- Symbol.unscopables
- Symbol.species
- Symbol.toPrimitive
- Symbol.toStringTag
- Symbol.for()
- Symbol.keyFor()
- Symbol.prototype.toString()
- Symbol.prototype.valueOf()
- Symbol.prototype.description
- Symbol 实际应用
- 参考
Symbol
是ES6开始新增的基本数据类型(primitive data type).它最大的特点是每一个Symbol([description])
函数返回symbol
类型的值是唯一的.它可以作为对象的key以避免key冲突.
1 | const symbol1 = Symbol(); // 可以不传任何参数 |
Symbol
是不能使用new
操作符的! 如果使用new
操作符,JS引擎会提示:’TypeError: Symbol is not a constructor’.
symbol
是基本数据类型,并不是对象所以不能用new
操作符,并且不能给symbol
值添加属性.
Symbol
不能被继承,子类化.
如果description
是个对象,则调用该对象的toString
方法,将其转为字符串,然后才生成一个 Symbol 值。
1 | const obj = { |
Symbol 属性和方法
Symbol.asyncIterator
Symbol.asyncIterator
符号指定了一个对象的默认异步迭代器。如果一个对象设置了这个属性,它就是异步可迭代对象,可用于for await...of
循环。
一个异步可迭代对象必须要有Symbol.asyncIterator
属性。
1 | const myAsyncIterable = new Object(); |
Symbol.iterator
Symbol.iterator 为每一个对象定义了默认的迭代器。该迭代器可以被 for...of
循环使用
1 | var myIterable = {} |
一个数据结构只要部署了Symbol.iterator
属性,就被视为具有 iterator 接口,就可以用for...of
循环遍历它的成员。也就是说,for...of
循环内部调用的是数据结构的Symbol.iterator
方法。
数组原生具备iterator
接口(即默认部署了Symbol.iterator
属性),for...of
循环本质上就是调用这个接口产生的遍历器,可以用下面的代码证明。
1 | const arr = ['red', 'green', 'blue']; |
Symbol.match
Symbol.match
用于标识对象是否具有正则表达式的行为。比如, String.prototype.startsWith()
,String.prototype.endsWith()
和 String.prototype.includes()
这些方法会检查其第一个参数是否是正则表达式,是正则表达式就抛出一个TypeError
。现在,如果 match
symbol 设置为 false
(或者一个 假值),就表示该对象不打算用作正则表达式对象。
1 | "/bar/".startsWith(/bar/); |
但是,如果你将 Symbol.match
置为 false
,使用 match
属性的表达式检查会认为该象不是正则表达式对象。startsWith
和 endsWith
方法将不会抛出 TypeError
。
1 | var re = /foo/; |
对象的Symbol.match
属性,指向一个函数。当执行str.match(myObject)
时,如果该属性存在,会调用它,返回该方法的返回值。
1 | String.prototype.match(regexp) |
Symbol.replace
Symbol.replace
这个属性指定了当一个字符串替换所匹配字符串时所调用的方法。String.prototype.replace()
方法会调用此方法。
1 | const x = {}; |
Symbol.search
对象的Symbol.search
属性,指向一个方法,当该对象被String.prototype.search
方法调用时,会返回该方法的返回值。
1 | String.prototype.search(regexp) |
Symbol.split
对象的Symbol.split
属性,指向一个方法,当该对象被String.prototype.split
方法调用时,会返回该方法的返回值。
1 | String.prototype.split(separator, limit) |
Symbol.hasInstance
Symbol.hasInstance
用于判断某对象是否为某构造器的实例。因此你可以用它自定义 instanceof
操作符在某个类上的行为。
1 | class MyArray { |
上面代码中,MyClass
是一个类,new MyClass()
会返回一个实例。该实例的Symbol.hasInstance
方法,会在进行instanceof
运算时自动调用,判断左侧的运算子是否为Array
的实例。
1 | class Even { |
Symbol.isConcatSpreadable
对象的Symbol.isConcatSpreadable
属性等于一个布尔值,表示该对象用于Array.prototype.concat()
时,是否可以展开。
1 | let arr1 = ['c', 'd']; |
上面代码说明,数组的默认行为是可以展开,Symbol.isConcatSpreadable
默认等于undefined
。该属性等于true
时,也有展开的效果。
上面代码说明,数组的默认行为是可以展开,Symbol.isConcatSpreadable
默认等于undefined
。该属性等于true
时,也有展开的效果。
1 | let obj = {length: 2, 0: 'c', 1: 'd'}; |
Symbol.isConcatSpreadable
属性也可以定义在类里面。
1 | class A1 extends Array { |
上面代码中,类A1
是可展开的,类A2
是不可展开的,所以使用concat
时有不一样的结果。
注意,Symbol.isConcatSpreadable
的位置差异,A1
是定义在实例上,A2
是定义在类本身,效果相同。
Symbol.unscopables
对象的Symbol.unscopables
属性,指向一个对象。该对象指定了使用with
关键字时,哪些属性会被with
环境排除。
1 | Array.prototype[Symbol.unscopables] |
上面代码说明,数组有 7 个属性,会被with
命令排除。
1 | // 没有 unscopables 时 |
上面代码通过指定Symbol.unscopables
属性,使得with
语法块不会在当前作用域寻找foo
属性,即foo
将指向外层作用域的变量。
Symbol.species
对象的Symbol.species
属性,指向一个构造函数。
1 | class MyArray extends Array { |
上面代码中,子类MyArray
继承了父类Array
,a
是MyArray
的实例,b
和c
是a
的衍生对象。你可能会认为,b
和c
都是调用数组方法生成的,所以应该是数组(Array
的实例),但实际上它们也是MyArray
的实例。
1 | class Array1 extends Array { |
Symbol.toPrimitive
对象的Symbol.toPrimitive
属性,指向一个方法。该对象被转为原始类型的值时,会调用这个方法,返回该对象对应的原始类型值。
Symbol.toPrimitive
被调用时,会接受一个字符串参数,表示当前运算的模式,一共有三种模式。
- Number:该场合需要转成数值
- String:该场合需要转成字符串
- Default:该场合可以转成数值,也可以转成字符串
1 | // 一个没有提供 Symbol.toPrimitive 属性的对象,参与运算时的输出结果 |
Symbol.toStringTag
Symbol.toStringTag
是一个内置 symbol,它通常作为对象的属性键使用,对应的属性值应该为字符串类型,这个字符串用来表示该对象的自定义类型标签,通常只有内置的 Object.prototype.toString()
方法会去读取这个标签并把它包含在自己的返回值里。
许多内置的 JavaScript 对象类型即便没有 toStringTag
属性,也能被 toString()
方法识别并返回特定的类型标签,比如:
1 | Object.prototype.toString.call('foo'); // "[object String]" |
另外一些对象类型则不然,toString()
方法能识别它们是因为引擎为它们设置好了 toStringTag
标签:
1 | Object.prototype.toString.call(new Map()); // "[object Map]" |
但你自己创建的类不会有这份特殊待遇,toString()
找不到 toStringTag
属性时只好返回默认的 Object
标签:
1 | class ValidatorClass {} |
Symbol.for()
Symbol.for(key)
方法会根据给定的键 key
,来从运行时的 symbol 注册表中找到对应的 symbol,如果找到了,则返回它,否则,新建一个与该键关联的 symbol,并放入全局 symbol 注册表中。
1 | Symbol.for("foo"); // 创建一个 symbol 并放入 symbol 注册表中,键为 "foo" |
Symbol.keyFor()
Symbol.keyFor(sym)
方法用来获取 symbol 注册表中与某个 symbol 关联的键
sym
必选参数,存储在 symbol 注册表中的某个 symbol
如果全局注册表中查找到该symbol,则返回该symbol的key值,形式为string。如果symbol未在注册表中,返回undefined
1 | // 创建一个 symbol 并放入 Symbol 注册表,key 为 "foo" |
Symbol.prototype.toString()
toString()
方法返回当前 symbol 对象的字符串表示。Symbol
对象拥有自己的 toString
方法,因而遮蔽了原型链上的
Symbol
对象拥有自己的 toString
方法,因而遮蔽了原型链上的
1 | Symbol("foo") + "bar"; |
Symbol.prototype.valueOf()
valueOf()
方法返回当前 symbol 对象所包含的 symbol 原始值。
在 JavaScript 中,虽然大多数类型的对象在某些操作下都会自动的隐式调用自身的 valueOf()
方法或者 toString()
方法来将自己转换成一个原始值,但 symbol 对象不会这么干,symbol 对象无法隐式转换成对应的原始值
1 | Object(Symbol("foo")) + "bar"; |
Symbol.prototype.description
description
是一个只读属性,它会返回 Symbol
对象的可选描述的字符串。
1 | console.log(Symbol('desc').description); |
Symbol 实际应用
value值的唯一性
1 | const LEVEL_INFO = Symbol('INFO') |
私有的对象方法
Symbol
作为对象的key时,是不会被迭代的.从而可以创建一个私有的对象方法
1 | const print = Symbol('print') |
Symbol in Typescript
Typescript是完全支持的 symbol
1 | const sym = Symbol('foo') |
给一个变量设置 symbol
值,并通过 typeof 在声明变量的的symbol
值
1 | const PROD: unique symbol = Symbol('Production mode') |
参考
https://tc39.es/ecma262/#sec-symbol-objects
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Symbol
作者: Fynn
链接: https://fynn90.github.io/2017/08/10/ES6%E4%B8%AD%E7%9A%84Symbol/
本文采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可