Symbol
Symbol数据类型表示独一无二的值,Symbol值通过Symbol()函数生成。
let s = Symbol();
console.log(typeof s);//Symbol
Symbol函数前不能使用new命令,因为生成的Symbol是一个原始类型的值,不是对象。也就是说,由于Symbol值不是对象,所有不能添加属性。
- Symbol函数可以接受一个字符串作为参数,表示对Symbol实例的描述。
- 主要是为了在控制台显示,或转换为字符串时比较容易区分。
let s1 = Symbol('foo');
let s2 = Symbol('bar');
console.log(s1, s1.toString());//Symbol(foo) "Symbol(foo)"
console.log(s2, s2.toString());//Symbol(bar) “Symbol(bar)”
- 如果Symbol的参数是一个对象,就会调用其toString方法,将其转换为字符串,然后才生成一个Symbol值。
const obj = {
toString() {
return 'abc’;
}
};
const sym = Symbol(obj);
console.log(sym);//Symbol(abc)
Symbol函数的参数只表示当前Symbol值的描述。
相同参数的Symbol函数的返回值是不相等的。
let s1 = Symbol();
let s2 = Symbol();
console.log('s1==s2', s1 == s2);//false
let s1 = Symbol('foo');
let s2 = Symbol('foo');
console.log('s1==s2', s1 == s2);//false
Symbol值不能与其他类型的值进行运算,否则会报错。
var sym = Symbol('my')
console.log("your symbol is" + sym );//报错
//`your symbol is ${sym}` //报错
Symbol值可以显式的转换为字符串。
var sum = Symbol('my symbol')
String(sym)//'Symbol(my symbol)'
sym.toString()//'Symbol(my symbol)'
Symbol值可以转换为布尔值,但不能转换为数值。
var sum = Symbol()
Boolean(sym) //true
作为属性名的Symbol
- Symbol作为对象属性名保证不会出现同名的属性。
- 对一个对象由多个模块构成的情况非常有用,能防止一个键被不小心改写或覆盖
let proSym = Symbol();
let a = {};
a[proSym] = 'hello';
console.log(a[proSym]);//hello
let b = {
[proSym]: 'world'
};
console.log(b[proSym]);//world
let c = {};
Object.defineProperty(c, proSym, { value: 'here' });
//Object.defineProperty(obj, prop, descriptor)
//obj:要定义属性的对象。
//prop:要定义或修改的属性的名称或 Symbol 。
//descriptor:要定义或修改的属性描述符。
- Symbol值作为属性名时不能使用点运算符。
let proSym = Symbol();
let a = {};
a.proSym='Hello';
console.log(a[proSym]);
console.log(a['proSym']);
- 在对象内部使用Symbol值定义属性时,Symbol值必须放在方括号中。如果不放在方括号中,该属性的键名就是字符串,而不是该属性的Symbol。
let d = {
[proSym](name) {
console.log('hello' + name);
}
};
d[proSym]('xiaohua');
let proSym = Symbol();
let a = {};
a.proSym = "hello";
console.log(a[proSym]);//undefined
console.log(a["proSym"]);//hello
- Symbol类型可以用于定义一组常量,保证这组常量的值都是不相等的。
let log = {};
log.levels = {
DEBUG: Symbol('debug’),
INFO: Symbol('info’),
WARN: Symbol('warn')
};
console.log(log.levels.DEBUG, 'debug message');
console.log(log.levels.INFO, 'info message');
实例:消除魔术字符串
- 魔术字符串,指在代码中多次出现且与代码形成强耦合的某一个具体的字符串或数值。
风格良好的代码,应该尽量消除魔术字符串,而由含义清晰的变量代替。
function getArea(shape, options) {
let area = 0;
switch (shape) {
case 'Triangle':
area = 0.5 * options.width * options.height;
break;
case 'Square':
area = options.height ** 2;
break;
default:
throw new Error('undefined shape');
}
return area;
}//'Triangle'与'Square'都是魔术字符串
let r = getArea('Triangle', { width: 100, height: 100 });
console.log(r);
- 改进1:通过变量代替字符串
let shapeType = {
triangle: 'Triangle’,
square: 'Square'
};
function getArea(shape, options) {
let area = 0;
switch (shape) {
case shapeType.triangle:
console.log(shapeType.triangle);
area = 0.5 * options.width * options.height;
break;
case shapeType.square:
console.log(shapeType.square);
area = options.height ** 2;
break;
default:
throw new Error('undefined shape’);
}
return area;
}
r = getArea(shapeType.square, { height: 20 });
- 改进2:通过Symbol()代替字符串
shapeType = {
triangle: Symbol(),
square: Symbol()
};
r = getArea(shapeType.square, { height: 20 });
属性名的遍历
- Symbol作为属性名时,该属性不会出现在for…in和for…of循环中。也不会被Object.keys()、Object.getOwnPropertyNames()返回。
- 可以使用Object.getOwnPropertySymbols()方法获取指定对象的所有Symbol属性名。该方法返回一个数组,包含了当前对象的所有用作属性名的Symbol值。
let obj = {};
let a = Symbol('a');
let b = Symbol('b');
obj[a] = 'Hello';
obj[b] = 'World';
let objectSymbols = Object.getOwnPropertySymbols(obj);
console.log(objectSymbols);
比较三种方法遍历Symbol属性
let foo = Symbol('foo');
Object.defineProperty(obj, foo, {
value: 'foobar'
});
for (let i in obj) {
console.log(i);
}
let r1 = Object.getOwnPropertyNames(obj);
console.log(r1);
let r2 = Object.getOwnPropertySymbols(obj);
console.log(r2);
- Reflect.ownKeys方法可以返回所有类型的键名,包括常规键名和Symbol键名。
let o = {
[Symbol('my_key')]: 1,
enum: 2,
nonEnum: 3
};
let r3 = Reflect.ownKeys(o);
console.log(r3);
Symbol.for()与Symbol.keyFor()
- Symbol函数即使对相同字符串生成值都不相同,但有时希望重新使用同一个 Symbol值。
- Symbol.for方法接受一个字符串,然后搜索有没有以该参数作为名称的Symbol值,若有,则返回这个Symbol值,否则新建并返回一个以该字符串为名称的Symbol值。
- Symbol.keyFor方法返回一个已登记的Symbol类型值的key。
let s1 = Symbol('foo');
let s2 = Symbol('foo');
console.log(s1 == s2);
console.log(Symbol.keyFor(s1));
s1 = Symbol.for('foo');
s2 = Symbol.for('foo');
console.log(s1 === s2);
console.log(Symbol.keyFor(s1));
- 实例:模块的Singleton模式
- Singleton模式是指,调用一个类并且在任何时候都返回同一个实例,又称为“单例模式”。
- 例如,对于Node来说,模块文件可以看成是一个类,如何保证每次执行这个模块文件返回的都是同一个实例呢?
- 方案1:把实例放到顶层对象global的常规属性中。
//模块代码
function A(){
this.foo='hello';
}
if(!global._foo){
global._foo=new A();
}module.exports=global._foo;
//引用模块的代码
let a = require('./mod.js');
console.log(a._foo===global._foo);
- 方案2:把实例放到顶层对象global的Symbol属性中。
//模块代码
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];
//引用模块代码
let foo=Symbol.for('foo');
let a=require('./mod');
console.log(a===global[foo]);
内置的Symbol值
- Symbol.hasInstance属性
- 指向一个内部方法,对象使用instanceof运算符时会调用这个方法,判断该对象是否为某个构造函数的实例。
- 如,foo instanceof Foo则是在内部调用了FooSymbol.hasInstance。
- Symbol. isConcatSpreadable属性
- 表示该对象使用Array.prototype.concat()时是否可以展开。
- 该对象只能是数组或类似数组的对象。
- Symbol. species属性
- 指向当前对象的构造函数。
- 创建实例时默认会调用这个方法,即使用这个属性返回的函数当作构造函数来创建新的实例对象。
- Symbol.match属性
- 指向一个函数,当执行str.match(myObject)时,如果该属性存在,会调用它返回该方法的返回值。
- Symbol.replace属性
- 指向一个方法,当对象被String.prototype.replace方法调用时返回该方法的返回值。
- Symbol.search属性
- 指向一个方法,当对象被String.prototype.seach方法调用时返回该方法的返回值。
- Symbol.split属性
- 指向一个方法,当对象被String.prototype.split方法调用时返回该方法的返回值。
- Symbol.iterator属性
- 指向该对象的默认遍历器方法。
- Symbol.toPrimitive属性
- 指向一个方法,对象被转换为原始类型的值时会调用这个方法,返回该方法对应的原始类型值。
- Symbol.toStringTag属性
- 指向一个方法,在对象调用Object.prototype.toString方法时,如果这个属性存在,则返回值会出现在toString方法返回的字符串中,表示对象的类型。
- 如[object Object]、[object Array]
- Symbol.unscopables属性
- 指向一个对象,指定了使用with关键字时哪些属性会被with环境排除。