Symbol
1. 概述
Symbol 可以保证每个属性的名字都是独一无二的,从根本上防止属性名的冲突。
Symbol 是原始数据类型。
const s = Symbol();
console.log(typeof s); // 'symbol'
Symbol 函数前不能使用 new,否则会报错。
这是因为生成的 Symbol 是一个原始类型的值,不是对象,所以不能添加属性。它是一种类似于字符串的数据类型。
可以接受一个字符串作为参数,描述 Symbol 实例,便于区分。
const a = Symbol('a');
const b = Symbol('b');
console.log(a); // Symbol(a)
console.log(b); // Symbol(b)
console.log(a.toString()); // 'Symbol(a)'
console.log(b.toString()); // 'Symbol(b)'
如果字符串是对象,会先调用对象的 toString
方法,转成字符串再生成一个 Symbol
值。
console.log(Symbol({})); // Symbol([object Object]);
注:参数只是对实例的描述,相同参数的 Symbol 函数返回的值是不相等的。
const symbol1 = Symbol();
const symbol2 = Symbol();
console.log(symbol1 === symbol2); // false
const symbolA = Symbol('a');
const symbolB = Symbol('a');
console.log(symbolA === symbolB); // false
Symbol 值不能与其他类型的值进行运算,会报错。
const symbol = Symbol('my symbol');
console.log('Your name is ' + symbol); // Cannot convert a Symbol value to a string
可以显示地将 Symbol 值 转为字符串。
const symbol = Symbol('my symbol');
console.log(String(symbol)); // "Symbol(my symbol)"
console.log(symbol.toString()); // "Symbol(my symbol)"
也可以转为布尔值,但不能转为数值。
const symbol = Symbol('my symbol');
console.log(Boolean(symbol)); // true
console.log(!symbol); // false
console.log(Number(symbol)); // Cannot convert a Symbol value to a number
2. Symbol.prototype.description
const symbol = Symbol('my symbol');
上面的代码中,'my symbol' 就是 symbol 的 description。
读取这个描述,需要显示转为字符串:
console.log(String(symbol)); // "Symbol(my symbol)"
ES2019 提供了 description,可直接读取。
const symbol = Symbol('my symbol');
console.log(symbol.description); // 'my symbol'
3. 作为属性名的 Symbol
每个 Symbol 值都是不相等的,所以可以作为标识符用于对象的属性名,保证不会出现同名的属性。
// 第一种
const obj = {};
obj[symbol] = 'Hello';
// 第二种
const obj = {
[symbol]: 'Hello',
};
// 第三种
const obj = {};
Object.defineProperty(obj, symbol, {
value: 'Hello',
});
console.log(obj[symbol]); // 'Hello'
Symbol 值作为属性名时不能用点运算符。
const obj = {};
obj.symbol = 'Hello'; // <-- 点运算符后面总是字符串,所以不会读取 symbol 作为标识名所指代的值
console.log(obj[symbol]); // undefined
console.log(obj['symbol']); // 'Hello'
4. 实例:消除魔术字符串
const getArea = shape => {
switch (shape) {
case 'triangle': // triangle 是魔术字符串,用到好多次
console.log('triangle');
break;
}
};
getArea('triangle'); // 'triangle'
const shapeType = {
triangle: Symbol('triangle'), // 用 Symbol 值代替属性值,防止冲突
};
const getArea = shape => {
switch (shape) {
case shapeType.triangle: // 用变量代替魔术字符串
console.log(shape);
break;
}
};
getArea(shapeType.triangle); // Symbol(triangle)
5. 属性名的遍历
- for...in 遍历自身、继承、可枚举的属性
- Object.keys() 遍历自身、可枚举的属性键值
- Object.getOwnPropertyNames() 遍历自身、可枚举、不可枚举的属性名
Symbol 属性名不会出现在 for..in,for...of 循环中,也不会被 Object.keys() Object.getOwnPropertyNames() JSON.stringify() 返回。
Object.getOwnPropertySymbols() 可以获取指定对象的所有 Symobl 属性名。
const obj = {};
const sA = Symbol('a');
const sB = Symbol('b');
obj[sA] = 'Hello';
obj[sB] = 'World';
console.log(Object.getOwnPropertySymbols(obj)); // [Symbol(a), Symbol(b)]
6. Symbol.for() Symbol.keyFor()
Symbol.for() 检索有没有以传入参数作为名称的 Symbol 值,有就返回,没有就新建一个,并返回。
const s1 = Symbol.for('s');
console.log(s1); // Symbol(s)
const s2 = Symbol.for('s');
console.log(s2); // Symbol(s)
const s3 = Symbol('s');
console.log(s3); // Symbol(s)
console.log(s1 === s2); // true
console.log(s1 === s3); // false
const s1 = Symbol.for('s');
const s2 = Symbol('s');
console.log(Symbol.keyFor(s1)); // s
console.log(Symbol.keyFor(s2)); // undefined
7. 实例:模块的 Singleton 模式
略...
8. 内置的 Symbol 值
- Symbol.hasInstance
对象的 Symbol.hasInstance 属性指向内部的一个方法,调用 foo instanceof Foo
相当于调用了 Foo[Symbol.hasInstance](foo)
。
class MyClass {
[Symbol.hasInstance](foo) {
console.log(foo instanceof Array);
return foo instanceof Array;
}
}
[] instanceof new MyClass(); // true <-- 会自动调用 MyClass 的 Symbol.hasInstance 属性所指向的方法
- Symbol.isConcatSpreadable
这个属性是一个布尔值,表示该对象用于 Array.prototype.concat()
时,是否可展开。
const arr1 = [1, 2];
console.log(arr1[Symbol.isConcatSpreadable]); // undefined <-- 数组中 undefined 等同于 true 的效果
console.log([3, 4].concat(arr1)); // [3, 4, 1, 2]
const arr2 = [1, 2];
arr2[Symbol.isConcatSpreadable] = false; // <-- 设置为 false
console.log([3, 4].concat(arr2)); // [3, 4, [1, 2]] <-- 不可展开
类似数组的对象正好相反。
const obj1 = {
0: 'a',
1: 'b',
length: 2,
};
console.log(obj1[Symbol.isConcatSpreadable]); // undefined <-- 累数组的对象中 undefined 等同于 false 的效果
console.log([1, 2].concat(obj1)); // 【1, 2, {0: 'a', 1: 'b', length: 2}] <-- 不可展开
const obj2 = {
0: 'a',
1: 'b',
length: 2,
};
obj2[Symbol.isConcatSpreadable] = true; // <-- 设置为 true
console.log([1, 2].concat(obj2)); // [1, 2, "a", "b"] <-- 可展开
- Symbol.species
对象的 Symbol.species 指向一个构造函数。创建衍生对象时,会使用该属性。
class MyArray extends Array {}
const a = new MyArray(1, 2, 3);
console.log(a instanceof Array); // true
console.log(a instanceof MyArray); // true
const b = a.map(v => v);
const c = a.filter(v => v > 1);
console.log(b instanceof Array); // true
console.log(b instanceof MyArray); // true <-- b c 是 a 的衍生对象,应该是 Array 的实例,但实际上,b c 也是 MyArray 的实例
console.log(c instanceof Array); // true
console.log(c instanceof MyArray); // true <-- b c 是 a 的衍生对象,应该是 Array 的实例,但实际上,b c 也是 MyArray 的实例
Symbol.species 就是为了解决这个问题。
class MyArray extends Array {
static get [Symbol.species]() { // 设置 Symbol.species 属性,创建衍生对象时,会调用指定的函数作为构造函数
return Array;
}
}
const a = new MyArray(1, 2, 3);
console.log(a instanceof Array); // true
console.log(a instanceof MyArray); // true
const b = a.map(v => v);
const c = a.filter(v => v > 1);
console.log(b instanceof Array); // true
console.log(b instanceof MyArray); // false <-- b c 不再是 MyArray 的实例
console.log(c instanceof Array); // true
console.log(c instanceof MyArray); // false <-- b c 不再是 MyArray 的实例
用途:某些类库是在基类的基础上修改的,子类使用继承方法时,作者可能希望返回基类的实例,而不是子类的实例。
- Symbol.match
对象的 Symbol.match 指向一个函数,执行 str.macth(myObject) 时会调用它,返回该方法的返回值。
String.prototype.match(regexp);
// 等同于
regexp[Symbol.match](this);
class MyMatcher {
[Symbol.match](str) {
return 'Hello World'.indexOf(str);
}
}
const matcher = new MyMatcher();
console.log('e'.match(matcher)); // 1
console.log(matcher[Symbol.match]('e')); // 1
- Symbol.replace