Symbol作为对象属性
Symbol 作为对象属性的基本概念
Symbol 是 ECMAScript 6 引入的一种新的原始数据类型,表示独一无二的值。它可以作为对象的属性名,这是 Symbol 最重要的用途之一。传统的对象属性名只能是字符串,而 Symbol 提供了另一种选择。
const mySymbol = Symbol();
const obj = {};
obj[mySymbol] = 'Hello Symbol';
console.log(obj[mySymbol]); // 输出: Hello Symbol
创建 Symbol 属性
创建 Symbol 属性的方法很简单,只需要使用方括号语法将 Symbol 值作为属性名即可。需要注意的是,Symbol 值必须放在方括号中,不能使用点运算符。
const prop1 = Symbol('description');
const prop2 = Symbol('description');
const obj = {
[prop1]: 'Value 1',
[prop2]: 'Value 2'
};
console.log(obj[prop1]); // 输出: Value 1
console.log(obj[prop2]); // 输出: Value 2
Symbol 属性的特性
Symbol 属性有几个重要特性值得注意:
- 不可枚举性:默认情况下,Symbol 属性不会出现在
for...in
、Object.keys()
或Object.getOwnPropertyNames()
的返回结果中 - 唯一性:每个 Symbol 值都是唯一的,即使它们有相同的描述
- 非字符串属性:Symbol 属性不是字符串,这与传统属性不同
const sym = Symbol('test');
const obj = {
[sym]: 'symbol value',
regular: 'regular value'
};
// 不会输出 Symbol 属性
for (let key in obj) {
console.log(key); // 只输出: regular
}
console.log(Object.keys(obj)); // 输出: ['regular']
console.log(Object.getOwnPropertyNames(obj)); // 输出: ['regular']
获取 Symbol 属性
虽然 Symbol 属性默认不可枚举,但可以通过特定方法获取它们:
const sym1 = Symbol('sym1');
const sym2 = Symbol('sym2');
const obj = {
[sym1]: 'value1',
[sym2]: 'value2',
regular: 'regular value'
};
// 获取对象的所有 Symbol 属性
const symbolProps = Object.getOwnPropertySymbols(obj);
console.log(symbolProps); // 输出: [Symbol(sym1), Symbol(sym2)]
// 获取所有属性(包括 Symbol 和常规属性)
const allProps = Reflect.ownKeys(obj);
console.log(allProps); // 输出: ['regular', Symbol(sym1), Symbol(sym2)]
Symbol 属性的实际应用
Symbol 属性在实际开发中有多种用途:
1. 定义对象的私有成员
虽然 JavaScript 没有真正的私有属性,但 Symbol 可以用来模拟私有成员:
const _privateData = Symbol('privateData');
class MyClass {
constructor() {
this[_privateData] = 'secret';
}
getSecret() {
return this[_privateData];
}
}
const instance = new MyClass();
console.log(instance.getSecret()); // 输出: secret
console.log(instance[_privateData]); // 理论上可以访问,但需要知道 Symbol
2. 防止属性名冲突
当扩展第三方库的对象时,使用 Symbol 可以避免属性名冲突:
// 第三方库的对象
const libraryObject = {
id: 123,
name: 'Library Object'
};
// 我们的扩展
const ourExtension = Symbol('ourExtension');
libraryObject[ourExtension] = 'Our custom data';
// 不会影响原有属性
console.log(libraryObject.id); // 输出: 123
console.log(libraryObject[ourExtension]); // 输出: Our custom data
3. 定义元编程相关的属性
ES6 使用 Symbol 定义了一些特殊的"元"属性,如 Symbol.iterator
:
const iterableObject = {
[Symbol.iterator]: function* () {
yield 1;
yield 2;
yield 3;
}
};
for (const value of iterableObject) {
console.log(value); // 依次输出: 1, 2, 3
}
内置的 Symbol 值
ECMAScript 6 提供了一些内置的 Symbol 值,用于改变语言内部行为:
Symbol.hasInstance
自定义 instanceof
操作符的行为:
class MyArray {
static [Symbol.hasInstance](instance) {
return Array.isArray(instance);
}
}
console.log([] instanceof MyArray); // 输出: true
console.log({} instanceof MyArray); // 输出: false
Symbol.toPrimitive
自定义对象转换为原始值的行为:
const obj = {
[Symbol.toPrimitive](hint) {
if (hint === 'number') {
return 42;
}
if (hint === 'string') {
return 'forty two';
}
return true;
}
};
console.log(+obj); // 输出: 42 (hint 是 "number")
console.log(`${obj}`); // 输出: forty two (hint 是 "string")
console.log(obj + ''); // 输出: true (hint 是 "default")
Symbol.toStringTag
自定义 Object.prototype.toString
的返回值:
class Collection {
get [Symbol.toStringTag]() {
return 'Collection';
}
}
const coll = new Collection();
console.log(Object.prototype.toString.call(coll)); // 输出: [object Collection]
Symbol 属性与 JSON 序列化
Symbol 属性在 JSON 序列化时会被忽略:
const sym = Symbol('test');
const obj = {
[sym]: 'symbol value',
regular: 'regular value'
};
const jsonString = JSON.stringify(obj);
console.log(jsonString); // 输出: {"regular":"regular value"}
const parsedObj = JSON.parse(jsonString);
console.log(parsedObj); // 输出: {regular: "regular value"}
Symbol 属性的继承
Symbol 属性可以像常规属性一样被继承:
const sym = Symbol('inherited');
class Parent {
constructor() {
this[sym] = 'parent value';
}
}
class Child extends Parent {
getSymbolValue() {
return this[sym];
}
}
const child = new Child();
console.log(child.getSymbolValue()); // 输出: parent value
Symbol 属性的描述
创建 Symbol 时可以添加描述,这个描述可以通过 Symbol.prototype.description
获取:
const sym = Symbol('这是一个描述');
console.log(sym.description); // 输出: 这是一个描述
Symbol 属性的全局注册
Symbol 可以通过 Symbol.for()
方法在全局注册表中创建或获取:
// 创建全局 Symbol
const globalSym = Symbol.for('global.key');
// 获取同一个 Symbol
const sameSym = Symbol.for('global.key');
console.log(globalSym === sameSym); // 输出: true
// 检查 Symbol 是否在全局注册表中
console.log(Symbol.keyFor(globalSym)); // 输出: global.key
Symbol 属性与 Proxy
Symbol 属性可以与 Proxy 一起使用,实现更高级的元编程:
const sym = Symbol('intercepted');
const handler = {
get(target, prop) {
if (prop === sym) {
return 'Intercepted Symbol access';
}
return Reflect.get(...arguments);
}
};
const target = {};
const proxy = new Proxy(target, handler);
console.log(proxy[sym]); // 输出: Intercepted Symbol access
Symbol 属性的性能考虑
使用 Symbol 属性有一些性能上的优势:
- 不会参与常规的属性枚举,减少了遍历的开销
- 由于唯一性,可以避免属性名冲突导致的覆盖问题
- 在大型对象中,Symbol 属性可以提供更好的组织结构
// 创建大量属性
const obj = {};
const COUNT = 1000000;
// 常规属性
console.time('string properties');
for (let i = 0; i < COUNT; i++) {
obj[`prop${i}`] = i;
}
console.timeEnd('string properties');
// Symbol 属性
console.time('symbol properties');
for (let i = 0; i < COUNT; i++) {
const sym = Symbol(`prop${i}`);
obj[sym] = i;
}
console.timeEnd('symbol properties');
Symbol 属性的类型检查
在 TypeScript 中,可以使用 unique symbol
类型来确保 Symbol 的唯一性:
const sym1: unique symbol = Symbol('sym1');
const sym2: unique symbol = Symbol('sym2');
interface MyObject {
[sym1]: string;
[sym2]: number;
}
const obj: MyObject = {
[sym1]: 'hello',
[sym2]: 42
};
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益,请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:内置Symbol值
下一篇:Symbol的不可枚举特性