Symbol and the for...in loop
Basic Characteristics of the Symbol Type
Symbol is a new primitive data type introduced in ECMAScript 6, representing unique values. It is created using the Symbol()
function:
const sym1 = Symbol();
const sym2 = Symbol('description');
Each Symbol value is unique, even if the same description string is passed:
Symbol('foo') === Symbol('foo') // false
The primary use of Symbol is as an identifier for object properties, preventing property name conflicts. When adding properties to an object, using Symbol ensures existing properties are not overwritten:
const obj = {};
const mySymbol = Symbol();
obj[mySymbol] = 'value';
console.log(obj[mySymbol]); // "value"
Traditional Behavior of for...in Loops
In ES5 and earlier versions, the for...in
loop was used to iterate over all enumerable properties of an object (including inherited enumerable properties):
const obj = {
a: 1,
b: 2
};
for (const key in obj) {
console.log(key); // Outputs "a", "b" in sequence
}
Note that for...in
traverses enumerable properties on the prototype chain:
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); // Outputs "childProp", "parentProp" in sequence
}
Interaction Between Symbol Properties and for...in
ES6 specifies that Symbol-type properties are not enumerated by for...in
loops:
const obj = {
[Symbol('sym1')]: 'symbol value',
regularProp: 'regular value'
};
for (const key in obj) {
console.log(key); // Only outputs "regularProp"
}
This design ensures backward compatibility, preventing existing code from being affected by the introduction of Symbol properties. To retrieve all Symbol properties of an object, use Object.getOwnPropertySymbols()
:
const symKeys = Object.getOwnPropertySymbols(obj);
console.log(symKeys); // [Symbol(sym1)]
Comparison of Complete Property Traversal Methods
ES6 provides multiple ways to traverse object properties, each handling Symbol properties differently:
for...in
: Traverses enumerable string properties of the object itself and inherited ones, excluding Symbols.Object.keys()
: Returns an array of the object's own enumerable string properties, excluding Symbols.Object.getOwnPropertyNames()
: Returns an array of all the object's own string properties (including non-enumerable ones), excluding Symbols.Object.getOwnPropertySymbols()
: Returns an array of all the object's own Symbol properties (including non-enumerable ones).Reflect.ownKeys()
: Returns an array of all the object's own keys (including strings and Symbols, including non-enumerable ones).
const obj = {
[Symbol('sym')]: 'symbol',
enumProp: 'enumerable',
nonEnumProp: 'non-enumerable'
};
Object.defineProperty(obj, 'nonEnumProp', {
enumerable: false
});
// Comparison of traversal methods
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)]
Practical Application Scenarios
The fact that Symbol properties are not enumerated by for...in
makes them ideal for defining object metadata or internal implementation details:
// Define a metadata 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();
// Regular traversal does not expose metadata
for (const key in user) {
console.log(key); // Only outputs "name"
}
// But it can still be accessed via specific APIs
console.log(user[METADATA]); // {createdAt: Date, modifiedAt: Date}
Another common use case is implementing custom iterators:
const collection = {
items: [1, 2, 3],
[Symbol.iterator]: function* () {
for (const item of this.items) {
yield item;
}
}
};
// for...of uses Symbol.iterator, but for...in does not enumerate it
for (const item of collection) {
console.log(item); // 1, 2, 3
}
for (const key in collection) {
console.log(key); // "items"
}
Relationship with JSON Serialization
Symbol properties are not only excluded from for...in
enumeration but are also ignored during JSON serialization:
const obj = {
regular: 'value',
[Symbol('sym')]: 'symbol value'
};
console.log(JSON.stringify(obj)); // {"regular":"value"}
To serialize Symbol properties, a custom toJSON
method is needed:
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"}
Impact of the Global Symbol Registry
Symbols created with Symbol.for()
are added to the global registry, but this does not affect their behavior in for...in
:
const globalSym = Symbol.for('global');
const obj = {
[globalSym]: 'global symbol value',
localProp: 'local value'
};
for (const key in obj) {
console.log(key); // Still only outputs "localProp"
}
Global Symbols still require specific APIs for access:
console.log(obj[Symbol.for('global')]); // "global symbol value"
Symbol Properties in Classes
In ES6 classes, Symbol properties are also not enumerated by 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); // Only outputs "publicProp"
}
console.log(instance.getPrivate()); // "private"
Symbol Properties on the Prototype Chain
Symbol properties on the prototype chain are also not enumerated by 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); // Only outputs "regularMethod"
}
child[METHOD](); // Can be called normally: "Parent method"
Performance Considerations
Since for...in
does not need to handle Symbol properties, its performance characteristics remain consistent with ES5. When an object contains a large number of Symbol properties, it does not affect the traversal speed of for...in
:
const obj = {};
const count = 100000;
// Add a large number of Symbol properties
for (let i = 0; i < count; i++) {
obj[Symbol(i)] = i;
}
// Add a regular property
obj.regularProp = 'value';
console.time('for...in');
for (const key in obj) {
// Only traverses regularProp
}
console.timeEnd('for...in'); // Very fast, unaffected by the number of Symbols
console.time('getOwnPropertySymbols');
Object.getOwnPropertySymbols(obj);
console.timeEnd('getOwnPropertySymbols'); // Slows down as the number of Symbols increases
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:Symbol的不可枚举特性
下一篇:Symbol的元编程能力