为什么 Symbol 是 JavaScript 的独特类型
JavaScript 的 Symbol 是 ES6 引入的一种原始数据类型,代表一个唯一不可重复的值。与字符串不同,每个 Symbol 都是唯一的,这让它成为实现私有属性、扩展内置行为的理想选择。
理解 Symbol,特别是 Well-Known Symbols,是理解 JavaScript 元编程能力的关键。
一、Symbol 基础
1.1 创建 Symbol
// 使用 Symbol() 创建
const sym1 = Symbol();
const sym2 = Symbol();
console.log(sym1 === sym2); // false — 每个 Symbol 都唯一
// 可选描述
const sym = Symbol('description');
console.log(sym.description); // 'description'
1.2 Symbol 的特性
// Symbol 是原始类型
typeof Symbol(); // 'symbol'
// Symbol 不可被 for...in 遍历
const obj = { [Symbol()]: 'value' };
for (let key in obj) {
console.log(key); // 不输出(Symbol 属性被忽略)
}
// Object.keys() 也获取不到 Symbol 属性
Object.keys(obj); // []
// 需要用 Object.getOwnPropertySymbols
Object.getOwnPropertySymbols(obj); // [Symbol()]
1.3 Symbol 注册表
// Symbol.for() 在全局注册表创建/获取 Symbol
const globalSym = Symbol.for('global');
const anotherGlobalSym = Symbol.for('global');
console.log(globalSym === anotherGlobalSym); // true — 同一个 Symbol
// Symbol.keyFor() 获取全局 Symbol 的键
console.log(Symbol.keyFor(globalSym)); // 'global'
// 普通 Symbol 没有键
const localSym = Symbol('local');
console.log(Symbol.keyFor(localSym)); // undefined
二、Well-Known Symbols
Well-Known Symbols 是 JavaScript 内置的 Symbol,用于定义对象在特定操作中的行为。
2.1 Symbol.iterator
定义对象的默认迭代器:
const collection = {
items: [1, 2, 3],
[Symbol.iterator]() {
let index = 0;
return {
next: () => {
if (index < this.items.length) {
return { value: this.items[index++], done: false };
}
return { value: undefined, done: true };
}
};
}
};
for (const item of collection) {
console.log(item); // 1, 2, 3
}
2.2 Symbol.toStringTag
自定义 Object.prototype.toString 的输出:
class Person {
get [Symbol.toStringTag]() {
return 'Person';
}
}
const p = new Person();
console.log(p.toString()); // '[object Person]'
console.log(Object.prototype.toString.call(p)); // '[object Person]'
2.3 Symbol.hasInstance
自定义 instanceof 的行为:
class EvenNumber {
static [Symbol.hasInstance](num) {
return typeof num === 'number' && num % 2 === 0;
}
}
console.log(4 instanceof EvenNumber); // true
console.log(3 instanceof EvenNumber); // false
2.4 Symbol.toPrimitive
自定义对象的类型转换:
class Temperature {
constructor(celsius) {
this.celsius = celsius;
}
[Symbol.toPrimitive](hint) {
if (hint === 'number') {
return this.celsius;
}
if (hint === 'string') {
return `${this.celsius}°C`;
}
return this.celsius;
}
}
const temp = new Temperature(25);
console.log(+temp); // 25 (number)
console.log(`${temp}`); // '25°C' (string)
console.log(temp + 0); // 25 (default)
2.5 Symbol.species
定义 Array 等方法的返回类型:
class MyArray extends Array {
static get [Symbol.species]() {
return Array; // 方法返回普通 Array
}
}
const arr = new MyArray(1, 2, 3);
const mapped = arr.map(x => x * 2);
console.log(mapped instanceof MyArray); // false
console.log(mapped instanceof Array); // true
三、使用 Symbol 实现私有属性
3.1 Symbol 作为私有属性名
const _password = Symbol('password');
const _authenticate = Symbol('authenticate');
class User {
constructor(name, password) {
this.name = name;
this[_password] = password;
}
[_authenticate](input) {
return this[_password] === input;
}
}
const user = new User('Alice', 'secret');
console.log(user.name); // 'Alice'
console.log(user._password); // undefined — 访问不到
console.log(user[Symbol.for('password')]); // undefined — 不是同一个 Symbol
3.2 Symbol 私有属性的限制
Symbol 私有不是真正的私有——可以通过 Object.getOwnPropertySymbols 访问:
const symbols = Object.getOwnPropertySymbols(user);
console.log(user[symbols[0]]); // 'secret' — 可以访问
真正的私有属性应该用 ES2022 的 #private 语法。
四、常见 Well-Known Symbols 列表
| Symbol | 用途 |
|---|---|
| Symbol.iterator | 定义默认迭代器 |
| Symbol.asyncIterator | 定义异步迭代器 |
| Symbol.toStringTag | 自定义 toString 输出 |
| Symbol.hasInstance | 自定义 instanceof |
| Symbol.toPrimitive | 自定义类型转换 |
| Symbol.species | 定义方法返回类型 |
| Symbol.isConcatSpreadable | 控制 concat 是否展开 |
| Symbol.unscopables | 控制 with 语句的行为 |
| Symbol.match | 自定义 matchAll 和 String.prototype.match |
| Symbol.replace | 自定义 String.prototype.replace |
| Symbol.search | 自定义 String.prototype.search |
| Symbol.split | 自定义 String.prototype.split |
五、面试高频考点
考点 1:Symbol 的特性
Symbol 是唯一不可重复的原始类型,不能被 for...in 遍历,可以通过 Symbol.for 在全局注册表中共享。
考点 2:Well-Known Symbols 的作用
Well-Known Symbols 定义对象在内置操作中的行为,如迭代、类型转换、instanceof 等。
考点 3:Symbol 与私有属性
Symbol 可以作为属性名实现"伪私有",但不是真正的私有,可以通过 Object.getOwnPropertySymbols 访问。