Symbol是创建唯一键的原始类型,核心作用是避免属性名冲突;每次Symbol()调用返回全新值,Symbol.for()实现全局共享;支持模拟私有字段、迭代协议等场景。

Symbol 是用来创建唯一键的原始类型
它最核心的作用是避免属性名冲突。当你在对象上添加属性,又不希望被外部代码意外覆盖或枚举到时,Symbol 就是天然解法——每次调用 Symbol() 都返回一个全新、互不相等的值,哪怕描述相同。
常见错误是误以为 Symbol('foo') === Symbol('foo') 为真,其实永远是 false;只有用 Symbol.for('foo') 才能跨调用共享同一个 Symbol。
-
Symbol():局部唯一,适合私有属性场景(如内部状态标记) -
Symbol.for('key'):全局注册,适合跨模块通信(如自定义事件类型) -
Object.getOwnPropertySymbols()能拿到所有 Symbol 键,但for...in和JSON.stringify()都忽略它们
用 Symbol 实现类私有字段(ES2015+ 兼容方案)
在 class 还没原生支持 #private 字段时,开发者普遍用 Symbol 模拟私有成员。它不改变运行时行为,但能显著降低误用概率。
const _id = Symbol('id');
const _name = Symbol('name');
class User {
constructor(id, name) {
this[_id] = id;
this[_name] = name;
}
getId() { return this[_id]; }
}
注意:这种“私有”只是约定层面的——通过 Object.getOwnPropertySymbols(obj) 仍可读取,所以它防的是疏忽,不是恶意访问。
立即学习“Java免费学习笔记(深入)”;
- 别把 Symbol 存在公共变量里到处传,容易泄漏;建议封装在模块作用域内
- 不要用
Symbol.toString()当字符串拼接,它返回类似"Symbol(foo)"的格式,不可靠 - V8 引擎对 Symbol 键的属性访问略慢于字符串键,高频场景需留意
Symbol.iterator 让对象支持 for...of
这是最实用的内置 Symbol。只要对象的 [Symbol.iterator] 方法返回一个符合迭代器协议的对象(含 next()),就能被 for...of、展开运算符、Array.from() 等识别。
const range = {
from: 1,
to: 3,
[Symbol.iterator]() {
return {
current: this.from,
last: this.to,
next() {
if (this.current <= this.last) {
return { value: this.current++, done: false };
}
return { done: true };
}
};
}
};
[...range]; // [1, 2, 3]
关键点在于:这个方法必须返回一个对象,且该对象的 next() 必须返回 {value: ..., done: ...} 结构。漏掉 done 或写错类型(比如返回 undefined)会导致无限循环或报错 TypeError: Iterator result undefined is not an object。
Symbol.toStringTag 影响 Object.prototype.toString.call() 输出
它不改变实际类型,只影响 toString() 的返回字符串,常用于调试或让自定义类看起来更“像”原生类型。
class MyArray {
get [Symbol.toStringTag]() {
return 'MyArray';
}
}
Object.prototype.toString.call(new MyArray()); // "[object MyArray]"
这个 Symbol 值必须是字符串,否则会被强制转成字符串,可能引发意外结果。另外,它不会影响 instanceof 或 typeof 判断——typeof new MyArray() 仍是 "object"。
真正容易被忽略的是:很多库(比如 Lodash 的 isPlainObject、Axios 的响应类型判断)会依赖 toStringTag 来区分对象种类,改它可能破坏第三方集成。











