概述
ES6 引入了一种新的原始数据类型Symbol
,表示独一无二的值。
它是 JavaScript 语言的第七种数据类型,前六种是:undefined
、null
、布尔值(Boolean
)、字符串(String
)、数值(Number
)、对象(Object
)。
注意,Symbol函数前不能使用new
命令,否则会报错。也就是说,由于 Symbol 值不是对象,所以不能添加属性。
Symbol函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分。
let s1 = Symbol('foo');
let s2 = Symbol('bar');
s1 // Symbol(foo)
s2 // Symbol(bar)
注意,Symbol函数的参数只是表示对当前Symbol
值的描述,因此相同参数的Symbol
函数的返回值是不相等的。
// 没有参数的情况
let s1 = Symbol();
let s2 = Symbol();
s1 === s2 // false
// 有参数的情况
let s1 = Symbol('foo');
let s2 = Symbol('foo');
s1 === s2 // false
Symbol 值不能与其他类型的值进行运算,会报错。
Symbol可以转换为字符串和布尔值,但是不能转换为数字
const s = Symbol('s');
s.toString();//"Symbol(s)"
!!s;// true
+s;// TypeError: Cannot convert a Symbol value to a number
作为属性名的Symbol
在对象的内部,使用 Symbol 值定义属性时,Symbol 值必须放在方括号之中。
let s = Symbol('s');
let a = {
[s]: 1
};
a[s] = 2;
注意,Symbol 值作为对象属性名时,不能用点运算符。因为点运算符后面总是字符串
消除魔术字符串
魔术字符串指的是,在代码之中多次出现、与代码形成强耦合的某一个具体的字符串或者数值。风格良好的代码,应该尽量消除魔术字符串,改由含义清晰的变量代替。
function test(val) {
switch (val) {
case 'value1':
{
alert(1);
break;
}
case 'value2':
{
alert(2);
break
}
}
}
test('value1')
上面代码中,字符串value1
就是一个魔术字符串。它多次出现,与代码形成“强耦合”,不利于将来的修改和维护。
常用的消除魔术字符串的方法,就是把它写成一个变量。
const value = {
value1: 'value1',
value2: 'value2'
};
function test(val) {
switch (val) {
case value.value1:
{
alert(1);
break;
}
case value.value2:
{
alert(2);
break
}
}
}
test(value.value1)
如果仔细分析,可以发现value.value1
等于哪个值并不重要,只要确保不会跟其他属性的值冲突即可。因此,这里就很适合改用Symbol
值。
const value = {
value1: Symbol(),
value2: Symbol()
};
function test(val) {
switch (val) {
case value.value1:
{
alert(1);
break;
}
case value.value2:
{
alert(2);
break
}
}
}
test(value.value1)
属性名遍历
作为属性名的Symbol需要使用Object.getOwnPropertySymbols()
进行遍历
也可以使用Reflect.ownKeys
进行遍历
Symbol.for()
Symbol.for()
与Symbol()
这两种写法,都会生成新的 Symbol。它们的区别是,前者会被登记在全局环境中供搜索,后者不会。
Symbol.for("bar") === Symbol.for("bar")
// true
Symbol("bar") === Symbol("bar")
// false
Symbol.for()
不会每次调用就返回一个新的 Symbol 类型的值,而是会先检查给定的key
是否已经存在,如果不存在才会新建一个值。比如,如果你调用Symbol.for("cat")
30 次,每次都会返回同一个 Symbol 值,但是调用Symbol("cat")30
次,会返回 30 个不同的 Symbol 值。
Symbol.for
为 Symbol 值登记的名字,是全局环境的,可以在不同的 iframe 或 service worker 中取到同一个值。
Symbol.keyFor()
Symbol.keyFor
方法返回一个已登记的 Symbol 类型值的key。
Symbol.keyFor(Symbol.for('x')); // "x"
Symbol.keyFor(Symbol('x')); // undefined
模块的 Singleton 模式
对于Node来说,每个模块可以看成一个类,每次调用都返回同一个实例,就是模块的 Singleton 模式
原来的实现方法是将返回的实例放在全局(顶层)对象global
上
// mod.js
function A(){
this.foo = 'hello'
}
if(!global._foo){
global._foo = new A()
}
module.exports = global._foo;
然后加载mod.js
const mod = require('./mod.js');
console.log(mod.foo)
但是全局变量global._foo
很容易被改写
为了解决这个问题可以使用Symbol.for
// mod.js
const FOO_KEY = Symbol.for('foo');
function A(){
this.foo = 'hello'
}
if(!global[FOO_KEY]){
global[FOO_KEY] = new A()
}
module.exports = global[FOO_KEY];
这样保证global[Symbol.for('foo')]
不会被无意间覆盖,但还是可以被改写,因为n个Symbol.for('foo')
都指向同一个注册值
如果使用Symbol作为属性名
// mod.js
const FOO_KEY = Symbol('foo');
function A(){
this.foo = 'hello'
}
if(!global[FOO_KEY]){
global[FOO_KEY] = new A()
}
module.exports = global[FOO_KEY];
n个Symbol('foo')
指向n个值,那么在外部是无法引用global[Symbol('foo')]
的,因为每次引用global[Symbol('foo')]
都相当于创建了一个新值(在mod.js内部通过global[FOO_KEY]
是指向同一个值的,但FOO_KEY
是内部变量,外部访问不到)
外部将无法引用这个值,当然也就无法改写。
原理类似于:
let s = Symbol('a');
let a = {};
a[s] = 123;
a[s] = 555;
console.log(a); // {Symbol(a): 555}
let b = {};
b[Symbol('a')] = 123;
b[Symbol('a')] = 456;
console.log(b); // {Symbol(a): 123, Symbol(a): 456}
变量s
作为属性名,指向同一个值,而Symbol('a')
作为属性名,每次指向新值