Symbol
什么是 Symbol?
先看一个问题:
如果你写了一个类,想给类添加一个"私有"属性,防止别人直接用 . 访问,你会怎么做?
class User {
constructor() {
this.name = "张三";
this._password = "123456"; // 下划线约定:这是私有变量
}
}
const user = new User();
console.log(user._password); // 123456 —— 完全没有隐私!Symbol 就是来解决这个问题的!
class User {
constructor() {
this.name = "张三";
const privateData = Symbol("password"); // 用 Symbol 作为隐藏的 key
this[privateData] = "123456";
}
getPassword() {
return this[Symbol("password")]; // 错误!每次创建新的 Symbol,不一样!
}
}等等,这样还是不行... 所以 Symbol 的核心特点是:
Symbol 的三大特性
- 唯一性:
Symbol()每次调用都创建全新的值,即使描述相同也不相等 - 隐藏性:用 Symbol 作为对象的 key,不会被
for...in、Object.keys()遍历到 - 不可枚举:Symbol 作为 key 的属性是"隐藏"的
const sym1 = Symbol("key");
const sym2 = Symbol("key");
console.log(sym1 === sym2); // false —— 每次都是全新的!
const obj = { [sym1]: "value" };
console.log(obj[sym2]); // undefined —— sym1 和 sym2 不一样!
console.log(Object.keys(obj)); // [] —— 遍历不到!
// 正确用法:保存同一个 Symbol
const password = Symbol("password");
obj[password] = "secret";
obj[password]; // "secret"Symbol 解决了什么问题?
| 场景 | 不用 Symbol | 用 Symbol |
|---|---|---|
| 模拟私有属性 | _password 仍可被访问 | 属性隐藏,不可枚举 |
| 避免字符串键冲突 | 可能覆盖他人添加的属性 | 唯一值,不会冲突 |
| 定义"魔法字符串" | 用字符串,容易拼错 | 用 Symbol,精准控制 |
快速上手
// 创建一个 Symbol
const sym = Symbol("可选的描述");
// 查看类型
typeof sym; // "symbol"
// 不能用 new
new Symbol(); // TypeError!
// 可以用作对象的 key
const obj = { [sym]: "hello" };
obj[sym]; // "hello"一、Symbol 构造函数
1.1 基本特性
- 构造函数:%Symbol%
- 全局对象属性:
Symbol - 调用方式:作为函数调用(不使用
new) - 不可继承:不能作为类的父类被继承
1.2 Symbol([description])
// 语法
Symbol(description?)参数:
description: 可选的描述字符串
算法步骤:
- 如果
NewTarget不是undefined,抛出TypeError - 如果
description是undefined,descString为undefined - 否则,将
description转换为字符串 - 返回一个新的 Symbol,其
[[Description]]为descString
// 正确用法
const sym1 = Symbol();
const sym2 = Symbol("description");
const sym3 = Symbol("same description"); // 与 sym2 不相等!
// 错误用法
const sym = new Symbol(); // TypeError: Symbol is not a constructor二、Symbol 构造函数的属性
2.1 静态属性一览
| 属性 | 描述 | 用途 |
|---|---|---|
Symbol.asyncIterator | 知名符号 | 异步迭代器 |
Symbol.hasInstance | 知名符号 | instanceof 检测 |
Symbol.isConcatSpreadable | 知名符号 | Array.prototype.concat 展开控制 |
Symbol.iterator | 知名符号 | 迭代器 |
Symbol.match | 知名符号 | String.prototype.match |
Symbol.matchAll | 知名符号 | String.prototype.matchAll |
Symbol.replace | 知名符号 | String.prototype.replace |
Symbol.search | 知名符号 | String.prototype.search |
Symbol.species | 知名符号 | 创建派生对象 |
Symbol.split | 知名符号 | String.prototype.split |
Symbol.toPrimitive | 知名符号 | 转换为原始值 |
Symbol.toStringTag | 知名符号 | 对象字符串描述 |
Symbol.unscopables | 知名符号 | with 语句排除 |
2.2 静态方法
Symbol.for(key)
在全局 Symbol 注册表中注册或获取 Symbol。
特点:
- 跨 realm 共享(所有realm共享同一个 GlobalSymbolRegistry)
- 相同 key 返回相同 Symbol
- 注册后永不被垃圾回收
const sym1 = Symbol.for("global");
const sym2 = Symbol.for("global");
console.log(sym1 === sym2); // trueSymbol.keyFor(sym)
从全局 Symbol 注册表中获取 key。
const sym = Symbol.for("myKey");
console.log(Symbol.keyFor(sym)); // "myKey"
// 普通 symbol 会返回 undefined
const sym2 = Symbol("local");
console.log(Symbol.keyFor(sym2)); // undefined三、Symbol.prototype 属性
3.1 constructor
Symbol.prototype.constructor === Symbol // true3.2 description
获取 Symbol 的描述字符串(访问器属性)。
const sym = Symbol("myDescription");
console.log(sym.description); // "myDescription"
const sym2 = Symbol();
console.log(sym2.description); // undefined3.3 toString()
返回 Symbol 的字符串描述。
const sym = Symbol("test");
sym.toString(); // "Symbol(test)"
const sym2 = Symbol();
sym2.toString(); // "Symbol()"内部实现:SymbolDescriptiveString
- 获取
sym.[[Description]] - 如果是
undefined,设为空字符串 - 返回
"Symbol(" + desc + ")"
3.4 valueOf()
返回 Symbol 本身。
const sym = Symbol("test");
sym.valueOf() === sym; // true3.5 Symbol.toPrimitive
Symbol 的原始值转换。
const sym = Symbol("test");
+sym; // TypeError: Cannot convert a Symbol value to a primitive注意: Symbol 不能转换为数字或字符串,会抛出 TypeError。
3.6 [Symbol.toStringTag]
默认值为 "Symbol"。
const sym = Symbol("test");
sym[Symbol.toStringTag]; // "Symbol"
Object.prototype.toString.call(sym); // "[object Symbol]"四、Symbol 实例
4.1 实例内部槽
| 内部槽 | 描述 |
|---|---|
[[SymbolData]] | 存储实际的 Symbol 原始值 |
[[Prototype]] | 指向 Symbol.prototype |
4.2 实例继承关系
Symbol 实例
↓ [[Prototype]]
Symbol.prototype
↓ [[Prototype]]
Object.prototype
↓ [[Prototype]]
null五、知名符号详解
5.1 Symbol.iterator
用于定义对象的默认迭代器。
const iterable = {
*[Symbol.iterator]() {
yield 1;
yield 2;
yield 3;
}
};
for (const item of iterable) {
console.log(item); // 1, 2, 3
}5.2 Symbol.asyncIterator
用于定义对象的默认异步迭代器。
const asyncIterable = {
[Symbol.asyncIterator]() {
let i = 0;
return {
next() {
return Promise.resolve({
value: ++i,
done: i > 3
});
}
};
}
};
for await (const item of asyncIterable) {
console.log(item);
}5.3 Symbol.hasInstance
自定义 instanceof 行为。
class MyArray {
static [Symbol.hasInstance](instance) {
return Array.isArray(instance);
}
}
[] instanceof MyArray; // true5.4 Symbol.toPrimitive
自定义原始值转换。
const obj = {
[Symbol.toPrimitive](hint) {
if (hint === "number") return 42;
if (hint === "string") return "answer";
return "default";
}
};
+obj; // 42
`${obj}`; // "answer"
obj + ""; // "default"5.5 Symbol.species
用于创建派生对象的静态访问器。
class MyArray extends Array {
static get [Symbol.species]() {
return Array;
}
}
const arr = MyArray.from([1, 2, 3]);
const mapped = arr.map(x => x * 2);
// mapped 是普通 Array,不是 MyArray5.6 Symbol.toStringTag
自定义 Object.prototype.toString 的返回值。
class MyClass {
get [Symbol.toStringTag]() {
return "MyClass";
}
}
Object.prototype.toString.call(new MyClass()); // "[object MyClass]"5.7 Symbol.isConcatSpreadable
控制 Array.prototype.concat 是否展开。
const arr = [1, 2];
const notSpreadable = {
length: 2,
0: 3,
1: 4,
[Symbol.isConcatSpreadable]: false
};
arr.concat(notSpreadable); // [[1, 2], [3, 4]]六、全局 Symbol 注册表
6.1 GlobalSymbolRegistry
- 一个全局可用的 append-only List
- 所有 realm 共享
- 在任何 ECMAScript 代码执行前初始化为空 List
- 元素为 Record 结构:
{ [[Key]]: String, [[Symbol]]: Symbol }
6.2 抽象操作:KeyForSymbol(sym)
// 伪代码
function KeyForSymbol(sym) {
for (const e of GlobalSymbolRegistry) {
if (SameValue(e[[Symbol]], sym)) {
return e[[Key]];
}
}
return undefined;
}七、常见错误
7.1 不能用作构造函数
new Symbol(); // TypeError7.2 不能强制转换
String(Symbol("test")); // "Symbol(test)"
// 但
Symbol("test") + ""; // TypeError
+Symbol("test"); // TypeError
Symbol("test") * 2; // TypeError7.3 不能作为对象键(字面量)
const obj = {
[Symbol("key")]: "value" // 可以
};
// 但不能通过点运算符访问八、实用技巧
8.1 创建唯一值
const uniqueKey = Symbol("description");
const obj = {};
obj[uniqueKey] = "private data";8.2 模拟私有属性
const _private = Symbol("private");
class MyClass {
constructor() {
this[_private] = "secret";
}
}8.3 避免命名冲突
const mySymbol = Symbol("method");
obj[mySymbol] = () => "custom";8.4 检查是否为 Symbol
const sym = Symbol("test");
console.log(typeof sym); // "symbol"
console.log(sym instanceof Symbol); // true九、总结
创建方式:
Symbol()- 创建本地 Symbol
Symbol.for()- 创建/获取全局注册 Symbol
特性:
- 唯一性:相同描述的 Symbol 也不相等
- 不可枚举:不会被 for...in、Object.keys 枚举
- 不可强制转换:不能转为数字
知名符号:
- 共 13 个,用于自定义语言行为
- 都是不可写、不可枚举、不可配置
注册表:
Symbol.for()/Symbol.keyFor()成对使用- 注册的 Symbol 跨 realm 可访问
参考
- ECMAScript 2025 Language Specification Section 20.4
- Symbol 相关内置对象