Symbol

Symbol 对象

Symbol

在 JavaScript 中,Symbol 是一种基本数据类型,用于创建唯一的、不可变的值。Symbol 对象通常用于对象属性的键,以确保属性名的唯一性,避免命名冲突。以下是 Symbol 对象的一些主要特点和用法:

1. 创建 Symbol

使用 Symbol() 函数可以创建一个新的 Symbol 对象。Symbol 函数可以接受一个可选的描述字符串,用于描述 Symbol 的用途,但这个描述字符串并不影响 Symbol 的唯一性。

1
2
const mySymbol = Symbol('mySymbol');
console.log(mySymbol); // Symbol(mySymbol)

2. Symbol 的唯一性

每个通过 Symbol() 创建的 Symbol 都是唯一的,即使它们的描述字符串相同。

1
2
3
const symbol1 = Symbol('mySymbol');
const symbol2 = Symbol('mySymbol');
console.log(symbol1 === symbol2); // false

3. 使用 Symbol 作为对象属性键

Symbol 常用于对象属性的键,以确保属性名的唯一性,避免命名冲突。

1
2
3
4
5
6
7
const mySymbol = Symbol('mySymbol');
const obj = {
[mySymbol]: 'Hello',
name: 'Alice'
};
console.log(obj[mySymbol]); // Hello
console.log(obj.name); // Alice

4. Symbol 的全局注册表

Symbol.for() 方法可以用于从全局 Symbol 注册表中检索一个 Symbol,如果该 Symbol 不存在,则创建一个新的 Symbol 并将其添加到注册表中。Symbol.keyFor() 方法可以用于从全局注册表中检索一个 Symbol 的键名。

1
2
3
4
const symbol1 = Symbol.for('mySymbol');
const symbol2 = Symbol.for('mySymbol');
console.log(symbol1 === symbol2); // true
console.log(Symbol.keyFor(symbol1)); // mySymbol

5. Symbol 的内置值

JavaScript 提供了一些内置的 Symbol 值,这些值有特定的用途,例如 Symbol.iterator 用于定义对象的默认迭代器。

1
2
3
4
5
6
7
const myArray = [1, 2, 3];
// 使用 Symbol.iterator 获取数组的默认迭代器
const iterator = myArray[Symbol.iterator]();
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: undefined, done: true }

6. Symbol 的属性描述符

Symbol 属性的描述符与普通属性的描述符类似,但 Symbol 属性默认是不可枚举的(enumerable: false)。

1
2
3
4
5
const mySymbol = Symbol('mySymbol');
const obj = {
[mySymbol]: 'Hello'
};
console.log(Object.getOwnPropertyDescriptors(obj)); // { [Symbol(mySymbol)]: { value: 'Hello', writable: true, enumerable: false, configurable: true } }

7. Symbol 的使用场景

  • 避免命名冲突:在对象上定义私有属性,避免与外部代码或库中的属性名冲突。
  • 定义迭代器:使用 Symbol.iterator 定义对象的默认迭代器。
  • 定义钩子方法:使用 Symbol.toPrimitiveSymbol.toStringTag 等内置 Symbol 定义对象的钩子方法。

示例

假设有一个对象,我们希望在对象上定义一些私有属性,避免与外部代码冲突:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const _id = Symbol('id');
const _name = Symbol('name');
class User {
constructor(id, name) {
this[_id] = id;
this[_name] = name;
}
getId() {
return this[_id];
}
getName() {
return this[_name];
}
}
const user = new User(1, 'Alice');
console.log(user.getId()); // 1
console.log(user.getName()); // Alice
// 外部代码无法直接访问 _id 和 _name 属性
console.log(user[_id]); // undefined
console.log(user[_name]); // undefined

在这个示例中,使用 Symbol 定义了 _id_name 两个私有属性,外部代码无法直接访问这些属性,只能通过 getIdgetName 方法获取。

总结

Symbol 对象在 JavaScript 中提供了一种创建唯一值的方法,特别适用于对象属性的键,以确保属性名的唯一性,避免命名冲突。通过 Symbol,可以定义私有属性、自定义迭代器和钩子方法,增强代码的封装性和安全性。

Symbol.iterator

在 JavaScript 中,Symbol.iterator 是一个内置的 Symbol 值,用于定义对象的默认迭代器。它是实现 可迭代协议(Iterable Protocol) 的关键,使得对象可以通过 for...of 循环、... 扩展运算符或 Array.from() 等方式被遍历。

1. 核心概念

  • 可迭代对象(Iterable):实现了 Symbol.iterator 方法的对象,该方法返回一个迭代器对象
  • 迭代器对象(Iterator):必须包含一个 next() 方法,每次调用返回一个包含 valuedone 的对象。

2. 基本语法

1
2
3
4
5
6
7
8
9
10
const iterable = {
[Symbol.iterator]() {
// 返回一个迭代器对象
return {
next() {
// 返回 { value: any, done: boolean }
}
};
}
};

3. 示例:自定义可迭代对象

(1)简单数字范围迭代器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const range = {
start: 1,
end: 5,
[Symbol.iterator]() {
let current = this.start;
return {
next: () => {
if (current <= this.end) {
return { value: current++, done: false };
} else {
return { done: true };
}
}
};
}
};
// 使用 for...of 遍历
for (const num of range) {
console.log(num); // 输出 1, 2, 3, 4, 5
}

(2)生成斐波那契数列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const fibonacci = {
[Symbol.iterator]() {
let a = 0, b = 1;
return {
next() {
const value = a;
[a, b] = [b, a + b];
return { value, done: value > 100 }; // 输出小于 100 的斐波那契数
}
};
}
};
// 转换为数组
const fibNumbers = [...fibonacci]; // [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

4. 内置可迭代对象

JavaScript 中许多内置对象默认实现了 Symbol.iterator,例如:

  • 数组for (const item of [1, 2, 3])
  • 字符串for (const char of 'hello')
  • Map/Setfor (const [key, value] of new Map())
  • NodeListfor (const node of document.querySelectorAll('div'))

5. 手动调用迭代器

可以直接调用 Symbol.iterator 方法获取迭代器并逐步执行:

1
2
3
4
5
const arr = ['a', 'b'];
const iterator = arr[Symbol.iterator]();
console.log(iterator.next()); // { value: 'a', done: false }
console.log(iterator.next()); // { value: 'b', done: false }
console.log(iterator.next()); // { value: undefined, done: true }

6. 让普通对象可迭代

默认情况下,普通对象不是可迭代的(无法直接用 for...of 遍历)。通过实现 Symbol.iterator 可使其支持迭代:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const person = {
name: "Alice",
age: 30,
hobbies: ["coding", "hiking"],
[Symbol.iterator]() {
const entries = Object.entries(this);
let index = 0;
return {
next() {
if (index < entries.length) {
return { value: entries[index++], done: false };
} else {
return { done: true };
}
}
};
}
};
// 遍历对象的键值对
for (const [key, value] of person) {
console.log(key, value);
// 输出:
// name Alice
// age 30
// hobbies ["coding", "hiking"]
}

7. 与生成器(Generator)结合

生成器函数(function*)会自动返回一个迭代器,简化 Symbol.iterator 的实现:

1
2
3
4
5
6
7
8
9
10
const range = {
start: 1,
end: 3,
*[Symbol.iterator]() { // 生成器语法
for (let i = this.start; i <= this.end; i++) {
yield i;
}
}
};
console.log([...range]); // [1, 2, 3]

8. 注意事项

  1. 不可重复迭代:一个迭代器通常只能遍历一次,遍历完成后需重新获取迭代器。
  2. 性能优化:避免在迭代器中修改原对象结构(如增删元素),可能导致意外行为。
  3. 兼容性Symbol.iterator 是 ES6 特性,旧版浏览器需通过 Babel 等工具转译。

9. 实际应用场景

  • 自定义数据结构:为链表、树等结构实现迭代逻辑。
  • 分页数据遍历:逐页获取远程数据直到结束。
  • 惰性计算:按需生成值(如无限序列)。

总结

Symbol.iterator 是 JavaScript 迭代机制的核心,通过实现它可以让任何对象支持 for...of 等现代遍历语法。理解这一机制有助于编写更灵活、可扩展的代码,尤其适用于处理自定义数据集合或异步流式数据。