Symbol
Symbol (符号) 是 ES6 新增的基本数据类型。
- 符号实例是唯一、不可变的。
- 符号的用途是确保对象属性使用唯一标识符,不会发生属性冲突的危险。
const symbol = Symbol();
console.log("type", typeof symbol); // type symbol
console.log("value", symbol); // value Symbol()
基本使用
调用 Symbol 函数时,可以传入一个字符串参数作为对符号的描述(description)。但是,这个字符串参数与符号定义或标识完全无关:
const symbol1 = Symbol();
const symbol2 = Symbol();
console.log(symbol1 == symbol2); // false
const symbol3 = Symbol("foo");
const symbol4 = Symbol("foo");
console.log(symbol3 == symbol4); // false
如果参数不是字符串,会进行隐式类型转换:
const sym = Symbol({});
console.log(sym); // Symbol([object Object])
使用全局符号注册表
如果需要共享和重用符号实例,那么可以用一个字符串作为键,使用 Symbol.for 方法在全局符号注册表中创建并重用符号。
第一次使用某个字符串调用时,它会检查全局符号注册表,发现不存在对应的符号,于是就会创建一个新符号实例并添加到注册表中。后续使用相同字符串的调用同样会检查注册表,发现存在与该字符串对应的符号,然后就会返回该符号实例。
const fooGlobalSymbol = Symbol.for("foo"); // 创建新符号
const otherFooGlobalSymbol = Symbol.for("foo"); // 重用已有符号
console.log(fooGlobalSymbol === otherFooGlobalSymbol); // true
即使采用相同的符号描述,在全局注册表中定义的符号跟使用 Symbol 定义的符号也并不等同:
const localSymbol = Symbol("foo");
const globalSymbol = Symbol.for("foo");
console.log(localSymbol === globalSymbol); // false
全局注册表中的符号必须使用字符串键来创建,因此作为参数传给 Symbol.for 的任何值都会被转换为字符串。此外,注册表中使用的键同时也会被用作符号描述。
const emptyGlobalSymbol = Symbol.for();
console.log(emptyGlobalSymbol); // Symbol(undefined)
可以使用 Symbol.keyFor 来查询全局注册表,这个方法接收符号作为参数,返回该全局符号对应的字符串键。如果查询的不是全局符号,则返回 undefined:
// 创建全局符号
const s = Symbol.for("foo");
console.log(Symbol.keyFor(s)); // foo
如果传给 Symbol.keyFor 的不是符号,则该方法抛出 TypeError:
Symbol.keyFor(123); // TypeError: 123 is not a symbol
使用符号作为属性
凡是可以使用字符串或数值作为属性的地方,都可以使用符号。
const s1 = Symbol("a"),
s2 = Symbol("b"),
s3 = Symbol("c"),
s4 = Symbol("b");
const obj = { [s1]: "a" };
console.log(obj); // {Symbol(a): 'a'}
obj[s2] = "b";
console.log(obj); // {Symbol(a): 'a', Symbol(b): 'b'}
Object.defineProperty(obj, s3, { value: "c" });
console.log(obj); // {Symbol(a): 'a', Symbol(b): 'b', Symbol(c): 'c'}
Object.defineProperties(obj, { [s4]: { value: "d" } });
console.log(obj); // {Symbol(a): 'a', Symbol(b): 'b', Symbol(c): 'c', Symbol(b): 'd'}
因为符号属性是对内存中符号的一个引用,所以直接创建并用作属性的符号不会丢失。但是,如果没有显式地保存对这些属性的引用,那么必须遍历对象的所有符号属性才能找到相应的属性键:
const o = {
[Symbol("foo")]: "fooval",
[Symbol("bar")]: "barval",
};
console.log(o); // {Symbol(foo): "foo val", Symbol(bar): "bar val"}
const barSymbol = Object.getOwnPropertySymbols(o).find((symbol) =>
symbol.toString().match(/bar/)
);
console.log(barSymbol); // Symbol(bar)
遍历 Symbol 属性
const gender = Symbol("male");
const obj = {
name: "superman",
[gender]: "myGender",
};
console.log(obj[gender]); // myGender
- 使用 for in 遍历 obj 的常规属性:
const traverseObjByForIn = (obj) => {
for (let key in obj) {
console.log(key); // name
}
};
traverseObjByForIn(obj);
- 使用 Object.keys 遍历 obj 的常规属性:
const traverseObjByObjectKeys = (obj) => {
Object.keys(obj).forEach((key) => {
console.log(key); // name
});
};
traverseObjByObjectKeys(obj);
- 使用 Object.getOwnPropertyNames 获取所有常规属性:
const traverseObjByObjectGetOwnPropertyNames = (obj) => {
console.log("Object.getOwnPropertyNames :");
Object.getOwnPropertyNames(obj).forEach((key) => {
console.log(key); // name
});
};
traverseObjByObjectGetOwnPropertyNames(obj);
- 使用 Object.getOwnPropertySymbols 获取所有 Symbol 属性:
const traverseObjByObjectGetOwnPropertySymbols = (obj) => {
console.log("Object.getOwnPropertySymbols :");
Object.getOwnPropertySymbols(obj).forEach((key) => {
console.log(key); // Symbol(male)
});
};
traverseObjByObjectGetOwnPropertySymbols(obj);
- 使用 Reflect.ownKeys 获取所有属性:
const traverseObjByReflectOwnKeys = (obj) => {
console.log("Reflect.ownKeys :");
Reflect.ownKeys(obj).forEach((key) => {
console.log(key); // name Symbol(male)
});
};
traverseObjByReflectOwnKeys(obj);
- 使用 Object.getOwnPropertyDescriptors 获取所有属性的描述对象:
Object.getOwnPropertyDescriptors(obj);
{
name: {
value: 'superman',
writable: true,
enumerable: true,
configurable: true,
},
[Symbol(male)]: {
value: 'myGender',
writable: true,
enumerable: true,
configurable: true,
},
}
注意事项
- Symbol 值不能进行数学运算/字符串拼接:
const sum = Symbol();
console.log(sum + 1); // TypeError: Cannot convert a Symbol value to a number
console.log(sum + ""); // TypeError: Cannot convert a Symbol value to a string
- Symbol 值可以显式转化为 String / Boolean 类型,但不能转为 Number 类型:
const sum = Symbol();
console.log(String(sum)); // Symbol()
console.log(Boolean(sum)); // true
// console.log(Number(sum)); // TypeError: Cannot convert a Symbol value to a number