自ECMAScript 2015起,symbol成为了一种新的原生类型,就像number和string一样。
TypeScript 中的 Symbols 是一种独特且不可变的数据类型,每个 Symbol 都是唯一的。它们通常用于定义对象属性的键,并且不会与其他属性键发生冲突。
1. 创建 Symbol
可以使用 Symbol
函数来创建一个新的 Symbol。每个 Symbol 都是唯一的,即使它们的描述相同。
const sym1 = Symbol();
const sym2 = Symbol("description");
console.log(sym1); // Symbol()
console.log(sym2); // Symbol(description)
2. 使用 Symbol 作为对象属性键
由于 Symbol 是唯一的,它们非常适合作为对象属性键,以避免属性名冲突。
const sym = Symbol("key");
const obj = {
[sym]: "value"
};
console.log(obj[sym]); // "value"
3. 全局 Symbol 注册表
可以使用 Symbol.for
和 Symbol.keyFor
在全局注册表中创建和查找 Symbols。这样即使在不同的模块中也可以共享同一个 Symbol。
const globalSym1 = Symbol.for("globalKey");
const globalSym2 = Symbol.for("globalKey");
console.log(globalSym1 === globalSym2); // true
const key = Symbol.keyFor(globalSym1);
console.log(key); // "globalKey"
4. 内置的 Well-Known Symbols
JavaScript 和 TypeScript 定义了一些内置的 Symbol,这些 Symbol 被称为 Well-Known Symbols,它们可以用来修改内置行为。例如,Symbol.iterator
用于定义对象的默认迭代器。
const myIterable = {
[Symbol.iterator]() {
let step = 0;
return {
next() {
step++;
if (step <= 5) {
return { value: step, done: false };
} else {
return { value: undefined, done: true };
}
}
};
}
};
for (const value of myIterable) {
console.log(value); // 1, 2, 3, 4, 5
}
5. Symbol 属性不可枚举
使用 Object.keys
或 for...in
循环时,Symbol 属性不会被枚举出来。但是可以使用 Object.getOwnPropertySymbols
来获取对象的 Symbol 属性。
const sym = Symbol("key");
const obj = {
[sym]: "value",
regularKey: "regularValue"
};
console.log(Object.keys(obj)); // ["regularKey"]
console.log(Object.getOwnPropertySymbols(obj)); // [ Symbol(key) ]
6. 使用 Symbol 实现私有属性
尽管 TypeScript 提供了 private
关键字,但在运行时它不会真正将属性隐藏。使用 Symbol 可以实现某种程度的私有属性,因为 Symbol 属性不会被意外访问或覆盖。
const _name = Symbol("name");
class Person {
constructor(name: string) {
this[_name] = name;
}
getName() {
return this[_name];
}
}
const john = new Person("John");
console.log(john.getName()); // John
console.log(john[_name]); // Error: Property '_name' does not exist on type 'Person'
7. 使用 Symbol 实现枚举类型的扩展
Symbol 也可以用于扩展枚举类型,使其更灵活和安全。
enum Directions {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT"
}
const Diagonal = {
[Symbol.for("Up-Left")]: "UP-LEFT",
[Symbol.for("Up-Right")]: "UP-RIGHT",
[Symbol.for("Down-Left")]: "DOWN-LEFT",
[Symbol.for("Down-Right")]: "DOWN-RIGHT"
};
console.log(Directions.Up); // "UP"
console.log(Diagonal[Symbol.for("Up-Left")]); // "UP-LEFT"
8. 示例总结
综合以上使用方式,Symbols 可以在以下场景中大显身手:
- 避免对象属性名冲突:使用 Symbol 作为对象属性键,确保属性的唯一性。
- 创建私有属性:虽然不是完全的私有,但可以避免意外的访问或覆盖。
- 使用全局 Symbol 注册表:在多个模块之间共享同一个 Symbol。
- 修改内置行为:通过 Well-Known Symbols 修改内置对象的行为。
- 增强枚举类型:使用 Symbol 实现更灵活和安全的枚举类型扩展。
通过合理使用 Symbols,可以大大提升代码的健壮性和可维护性。