详解JavaScript原始数据类型Symbol

作者:木棉

时间:2026-10-24

浏览:100

本文深入解析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 资料请查阅本站其他相关文章!

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章