Symbol与for...in循环
Symbol类型的基本特性
Symbol是ECMAScript 6引入的新的原始数据类型,表示独一无二的值。它通过Symbol()
函数创建:
const sym1 = Symbol();
const sym2 = Symbol('description');
每个Symbol值都是唯一的,即使传入相同的描述字符串:
Symbol('foo') === Symbol('foo') // false
Symbol的主要用途是作为对象属性的标识符,防止属性名冲突。当需要向对象添加属性时,使用Symbol可以确保不会覆盖已有属性:
const obj = {};
const mySymbol = Symbol();
obj[mySymbol] = 'value';
console.log(obj[mySymbol]); // "value"
for...in循环的传统行为
在ES5及之前版本中,for...in
循环用于遍历对象的所有可枚举属性(包括继承的可枚举属性):
const obj = {
a: 1,
b: 2
};
for (const key in obj) {
console.log(key); // 依次输出 "a", "b"
}
需要注意的是,for...in
会遍历原型链上的可枚举属性:
function Parent() {
this.parentProp = 'parent';
}
function Child() {
this.childProp = 'child';
}
Child.prototype = new Parent();
const child = new Child();
for (const key in child) {
console.log(key); // 依次输出 "childProp", "parentProp"
}
Symbol属性与for...in的交互
ES6规定,Symbol类型的属性不会被for...in
循环枚举:
const obj = {
[Symbol('sym1')]: 'symbol value',
regularProp: 'regular value'
};
for (const key in obj) {
console.log(key); // 只输出 "regularProp"
}
这种行为设计是为了保持向后兼容性,避免现有代码因Symbol属性的引入而受到影响。如果要获取对象的所有Symbol属性,可以使用Object.getOwnPropertySymbols()
:
const symKeys = Object.getOwnPropertySymbols(obj);
console.log(symKeys); // [Symbol(sym1)]
完整属性遍历方法比较
ES6提供了多种遍历对象属性的方式,它们对Symbol属性的处理各不相同:
for...in
:遍历对象自身及继承的可枚举字符串属性,不包括SymbolObject.keys()
:返回对象自身可枚举字符串属性数组,不包括SymbolObject.getOwnPropertyNames()
:返回对象自身所有字符串属性数组(包括不可枚举),不包括SymbolObject.getOwnPropertySymbols()
:返回对象自身所有Symbol属性数组(包括不可枚举)Reflect.ownKeys()
:返回对象自身所有键的数组(包括字符串和Symbol,包括不可枚举)
const obj = {
[Symbol('sym')]: 'symbol',
enumProp: 'enumerable',
nonEnumProp: 'non-enumerable'
};
Object.defineProperty(obj, 'nonEnumProp', {
enumerable: false
});
// 各种遍历方法比较
console.log(Object.keys(obj)); // ["enumProp"]
console.log(Object.getOwnPropertyNames(obj)); // ["enumProp", "nonEnumProp"]
console.log(Object.getOwnPropertySymbols(obj)); // [Symbol(sym)]
console.log(Reflect.ownKeys(obj)); // ["enumProp", "nonEnumProp", Symbol(sym)]
实际应用场景
Symbol属性不可被for...in
枚举的特性,使其非常适合用于定义对象的元数据或内部实现细节:
// 定义元数据Symbol
const METADATA = Symbol('metadata');
class User {
constructor(name) {
this.name = name;
this[METADATA] = {
createdAt: new Date(),
modifiedAt: new Date()
};
}
update() {
this[METADATA].modifiedAt = new Date();
}
}
const user = new User('Alice');
user.update();
// 常规遍历不会暴露元数据
for (const key in user) {
console.log(key); // 只输出 "name"
}
// 但仍可通过特定API访问
console.log(user[METADATA]); // {createdAt: Date, modifiedAt: Date}
另一个常见用途是实现自定义迭代器:
const collection = {
items: [1, 2, 3],
[Symbol.iterator]: function* () {
for (const item of this.items) {
yield item;
}
}
};
// for...of会使用Symbol.iterator,但for...in不会枚举它
for (const item of collection) {
console.log(item); // 1, 2, 3
}
for (const key in collection) {
console.log(key); // "items"
}
与JSON序列化的关系
Symbol属性不仅不会被for...in
枚举,在JSON序列化时也会被忽略:
const obj = {
regular: 'value',
[Symbol('sym')]: 'symbol value'
};
console.log(JSON.stringify(obj)); // {"regular":"value"}
如果需要序列化Symbol属性,需要自定义toJSON
方法:
const obj = {
regular: 'value',
[Symbol('sym')]: 'symbol value',
toJSON() {
const plainObject = {};
for (const key in this) {
plainObject[key] = this[key];
}
plainObject.symbolValue = this[Symbol.for('sym')];
return plainObject;
}
};
console.log(JSON.stringify(obj)); // {"regular":"value","symbolValue":"symbol value"}
全局Symbol注册表的影响
使用Symbol.for()
创建的Symbol会被放入全局注册表,但这不影响它们在for...in
中的行为:
const globalSym = Symbol.for('global');
const obj = {
[globalSym]: 'global symbol value',
localProp: 'local value'
};
for (const key in obj) {
console.log(key); // 仍然只输出 "localProp"
}
全局Symbol仍然需要通过特定API访问:
console.log(obj[Symbol.for('global')]); // "global symbol value"
类中的Symbol属性
在ES6类中,Symbol属性同样不会被for...in
枚举:
const PRIVATE = Symbol('private');
class MyClass {
constructor() {
this.publicProp = 'public';
this[PRIVATE] = 'private';
}
getPrivate() {
return this[PRIVATE];
}
}
const instance = new MyClass();
for (const key in instance) {
console.log(key); // 只输出 "publicProp"
}
console.log(instance.getPrivate()); // "private"
原型链上的Symbol属性
原型链上的Symbol属性同样不会被for...in
枚举:
const METHOD = Symbol('method');
class Parent {
[METHOD]() {
console.log('Parent method');
}
}
class Child extends Parent {
regularMethod() {
console.log('Child method');
}
}
const child = new Child();
for (const key in child) {
console.log(key); // 只输出 "regularMethod"
}
child[METHOD](); // 可以正常调用 "Parent method"
性能考虑
由于for...in
不需要处理Symbol属性,其性能特性与ES5保持一致。当对象包含大量Symbol属性时,不会影响for...in
的遍历速度:
const obj = {};
const count = 100000;
// 添加大量Symbol属性
for (let i = 0; i < count; i++) {
obj[Symbol(i)] = i;
}
// 添加常规属性
obj.regularProp = 'value';
console.time('for...in');
for (const key in obj) {
// 只遍历到regularProp
}
console.timeEnd('for...in'); // 非常快,不受Symbol数量影响
console.time('getOwnPropertySymbols');
Object.getOwnPropertySymbols(obj);
console.timeEnd('getOwnPropertySymbols'); // 随Symbol数量增加而变慢
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益,请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:Symbol的不可枚举特性
下一篇:Symbol的元编程能力