何为 Symbol?
Symbol
是ES6 引入的一种新的原始数据类型,表示独一无二的值。它是 JavaScript 语言的第七种数据类型,前六种是:undefined
、null
、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)。
为什么引入?
因为 ES5 的对象属性名都是字符串,所以这就容易造成属性名的冲突。比如,当我们使用一个他人提供的对象,但又想为这个对象添加新的方法(mixin 模式)时,新方法的名字就有可能与现有方法产生冲突。如果有一种机制,保证每个属性的名字都是独一无二的就好了,这样就从根本上防止属性名的冲突。这就是 ES6 引入 Symbol
的原因。
语法
1 | Symbol([description]) |
构造方法 和 一些特性
1 | const symbol1 = Symbol(); |
Symbol 的构造函数并不完整,因为它 不支持 语法:
new Symbol()
。
1 var sym = new Symbol(); // TypeError
Symbol 的特点
唯一性
即使是同一个变量,生成的值也不相等:
1 | let id1 = Symbol("id"); |
全局共享的 Symbol
虽然 Symbol 保证了唯一性,但当我们想要多次使用同一个 Symbol 时,可以使用官方提供的全局注册并登记的方法 Symbol.for()
:
1 | let name1 = Symbol.for('name'); // 检测到未创建后,新建 |
可以使用 Symbol.keyFor()
来获取 Symbol 对象的参数值:
1 | let name1 = Symbol.for('name'); |
隐藏性
无法用 for...in
或 object.keys()
访问:
1 | let id = Symbol("id"); |
在对象中查找用 Symbol 标识的属性名
可以用 Object.getOwnPropertySymbols
方法访问,该方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值:
1 | let id = Symbol("id"); |
Symbol 有什么用?
每个从
Symbol()
返回的 symbol 值都是唯一的。一个 symbol 值能作为对象属性的标识符;这是该数据类型仅有的目的。
它本质上是一种 唯一标识符,可用作对象的唯一属性名,这样其他人就不会改写或覆盖你设置的属性值。
防止属性名冲突
作为属性名的 Symbol
由于每一个 Symbol 值都是不相等的,这意味着 Symbol 值可以作为标识符,用于对象的属性名,就能保证不会出现同名的属性。这对于一个对象由多个模块构成的情况非常有用,能防止某一个键被不小心改写或覆盖。
当一个 symbol 值作为对象属性的标识符,也就是说,将对象属性名指定为一个 Symbol 值时,有以下几种写法:
1 | let mySymbol = Symbol(); |
注意:Symbol 值作为对象属性名时,不能用点运算符。
1
2
3
4
5
6 const mySymbol = Symbol();
const a = {};
a.mySymbol = 'Hello!';
console.log(a[mySymbol]); // undefined
console.log(a['mySymbol']); // "Hello!"上面代码中,因为点运算符后面总是字符串,所以不会读取
mySymbol
作为标识名所指代的那个值,导致a
的属性名实际上是一个字符串,而不是一个 Symbol 值。
所以,在对象的内部,使用 Symbol 值定义属性时,Symbol 值必须放在方括号之中。如果不放在方括号中,那么该属性的键名就是一个字符串,而不是那个 Symbol 值。
实例:消除魔术字符串
魔术字符串指的是,在代码之中多次出现、与代码形成强耦合的某一个具体的字符串或者数值。
风格良好的代码,应该尽量消除魔术字符串,改由含义清晰的变量代替。
1 | function getArea(shape, options) { |
上面代码中,字符串 Triangle
就是一个魔术字符串。它多次出现,与代码形成“强耦合”,不利于将来的修改和维护。
常用的消除魔术字符串的方法是,把它写成一个变量。
1 | const shapeType = { |
上面代码中,我们把 'Triangle'
写成 shapeType
对象的 triangle
属性,这样就消除了强耦合。
如果仔细分析,可以发现 shapeType.triangle
等于哪个值并不重要,只要确保不会跟其他 shapeType
属性的值冲突即可。因此,这里就很适合改用 Symbol 值。
1 | const shapeType = { |
模拟私有属性
由于上文所说的,Symbol 具有隐藏性,所以我们可以很方便地用来模拟私有属性。
因为 symbol
不会出现在 Object.keys()
的结果中,因此除非你明确地 export
一个 symbol
,或者用 Object.getOwnPropertySymbols()
函数获取,否则其他代码是无法访问这个属性的。另外,symbol
也不会出现在 JSON.stringify()
的结果里,确切地说是 JSON.stringify()
会忽略 symbol
属性名和属性值。
1 | const symbol = Symbol('test'); |