详解JavaScript原始数据类型Symbol
本文深入解析JavaScript原始数据类型Symbol。介绍Symbol()函数的创建方法及唯一性,区分全局注册中心Symbol.for()与普通Symbol。探讨Symbol作为对象属性名避免命名冲突的机制,以及如何利用其不可枚举特性模拟私有属性和隔离用户数据。
概述
生成 symbol 实例最便捷的方式是调用 Symbol() 函数。symbol 类型具备两个显著特征:
1. 支持作为对象属性标识符。仅字符串与 symbol 类型可充当对象属性名。
2. 任意两个 symbol 值均不相等。
const symbol1 = Symbol();
const symbol2 = Symbol();
symbol1 === symbol2; // false
const obj = {};
obj[symbol1] = 'Hello';
obj[symbol2] = 'World';
obj[symbol1]; // 'Hello'
obj[symbol2]; // 'World'
尽管调用 Symbol() 使其外观类似对象,但 symbol 实为 JavaScript 原始数据类型。若尝试使用 new 关键字调用 Symbol(),将引发错误。
const symbol1 = Symbol();
typeof symbol1; // 'symbol'
symbol1 instanceof Object; // false
// Throws "TypeError: Symbol is not a constructor"
new Symbol();
描述信息
Symbol() 函数接收单一参数,即字符串 description。该参数仅用于辅助调试,体现于其 toString() 返回值中。需注意的是,即便两个 symbol 拥有相同的 description,它们依然不相等。
const symbol1 = Symbol('my symbol');
const symbol2 = Symbol('my symbol');
symbol1 === symbol2; // false
console.log(symbol1); // 'Symbol(my symbol)'
存在一个全局 symbol 注册表,通过 Symbol.for() 创建的 symbol 会被纳入此注册表,并以 description 作为索引键。换言之,若使用 Symbol.for() 创建具有相同 description 的两个 symbol,它们将判定为相等。
const symbol1 = Symbol.for('test');
const symbol2 = Symbol.for('test');
symbol1 === symbol2; // true
console.log(symbol1); // 'Symbol(test)'
一般而言,除非具备充分理由,否则不建议使用全局注册中心,因其可能导致命名冲突。
命名冲突
JavaScript 内置了一个 symbol,即 ES6 中的 Symbol.iterator。具备 Symbol.iterator 方法的对象被称为可迭代对象,允许在对象上执行 for/of 循环。
const fibonacci = {
[Symbol.iterator]: function*() {
let a = 1;
let b = 1;
let temp;
yield b;
while (true) {
temp = a;
a = a + b;
b = temp;
yield b;
}
}
};
// Prints every Fibonacci number less than 100
for (const x of fibonacci) {
if (x >= 100) {
break;
}
console.log(x);
}
此处为何选用 Symbol.iterator 而非字符串?假设未使用 Symbol.iterator,可迭代对象需具备一个名为 'iterator' 的字符串属性,如下述可迭代对象类所示:
class MyClass {
constructor(obj) {
Object.assign(this, obj);
}
iterator() {
const keys = Object.keys(this);
let i = 0;
return (function*() {
if (i >= keys.length) {
return;
}
yield keys[i++];
})();
}
}
MyClass 的实例为可迭代对象,可遍历其属性。然而,上述类存在潜在缺陷,假设恶意用户向 MyClass 构造函数传入包含 iterator 属性的对象:
const obj = new MyClass({ iterator: 'not a function' });
此时在 obj 上执行 for/of,JavaScript 将抛出 TypeError: obj is not iterable 异常。可见,传入对象的 iterator 函数覆盖了类的 iterator 属性。此情形类似于原型污染安全漏洞,盲目复制用户数据会对 __proto__ 和 constructor 等特殊属性造成问题。
核心在于,symbol 使对象内部数据与用户数据互不干扰。由于 symbol 无法在 JSON 中表示,故无需担忧向 Express API 传入包含不当 Symbol.iterator 属性的数据。此外,对于混合内置函数与用户数据的对象(如 Mongoose model),可利用 symbol 确保用户数据不与内置属性冲突。
私有属性
鉴于任意两个 symbol 均不相等,在 JavaScript 中可便捷地用于模拟私有属性。symbol 不出现在 Object.keys() 结果中,因此除非显式 export 该 symbol 或使用 Object.getOwnPropertySymbols() 函数获取,否则其他代码无法访问此属性。
function getObj() {
const symbol = Symbol('test');
const obj = {};
obj[symbol] = 'test';
return obj;
}
const obj = getObj();
Object.keys(obj); // []
// 除非有这个 symbol 的引用,否则无法访问该属性
obj[Symbol('test')]; // undefined
// 用 getOwnPropertySymbols() 依然可以拿到 symbol 的引用
const [symbol] = Object.getOwnPropertySymbols(obj);
obj[symbol]; // 'test'
另一原因是 symbol 不出现在 JSON.stringify() 结果中,确切地说 JSON.stringify() 会忽略 symbol 属性名及属性值:
const symbol = Symbol('test');
const obj = { [symbol]: 'test', test: symbol };
JSON.stringify(obj); // "{}"
总结
利用 Symbol 表示对象内部状态,可有效隔离用户数据与程序状态。借助它,我们不再需要某些命名约定,例如以 '$' 开头的内部属性。下次需定义私有属性时,建议尝试 Symbol 类型!
以上为详解 JavaScript 原始数据类型 Symbol 的详细内容,更多 JavaScript 原始数据类型 Symbo 资料请查阅本站其他相关文章!
JS中将数组和对象混合组装为JSON字符串
2026-11-05 16:41:42
JS运算符优先级与结合性的区别是什么?
2026-11-05 13:22:02
JS Map怎么只遍历Key或Value
2026-11-05 13:00:36
D3.js 与 ECharts 选型对比:灵活性 vs 开发效率
2026-11-05 09:45:01
为什么JS中typeof []返回object:数组类型检测误区解析
2026-11-04 19:50:47
