前端知库

Symbol 有什么用?常见的内置 Symbol 有哪些?

每次调用 Symbol() 都得到一个独一无二的值,用作属性键不会和别人的键冲突,也不会出现在 Object.keys、for...in 和 JSON.stringify 里。Symbol.for 从全局注册表取共享的 Symbol。内置 Symbol 如 Symbol.iterator、Symbol.toPrimitive、Symbol.toStringTag、Symbol.hasInstance 让对象定制语言行为。

参考答案

简答

给第三方传进来的对象挂一个内部标记,如果用字符串键 'id',可能和对象原有的 id 冲突。用 Symbol('id') 作键就不会,因为每次调用 Symbol() 都返回一个独一无二的值,哪怕描述一样也不相等。

Symbol 键还比较“隐蔽”,Object.keys、for...in、JSON.stringify 都不会列出它,要用 Object.getOwnPropertySymbols 或 Reflect.ownKeys 才能拿到。不过它不是私有属性,知道办法的代码照样能读到。

如果几个文件甚至几个 iframe 需要共用同一个 Symbol,就用 Symbol.for('key'),它从全局注册表里按键取,同一个键总是返回同一个 Symbol。

另一个重要用途是内置 Symbol,它们是语言留给对象的“钩子”。实现 Symbol.iterator 就能被 for...of 遍历,Symbol.toPrimitive 控制转成原始值的结果,Symbol.toStringTag 改变 Object.prototype.toString 的输出,Symbol.hasInstance 定制 instanceof。

唯一的属性键

JavaScript
const a = Symbol('id');
const b = Symbol('id');
console.log(a === b); // false
console.log(Symbol.for('app') === Symbol.for('app')); // true
const user = { name: '小明', [a]: 1 };
console.log(Object.keys(user)); // [ 'name' ]
console.log(JSON.stringify(user)); // {"name":"小明"}
console.log(Object.getOwnPropertySymbols(user).length); // 1
const money = {
  [Symbol.toPrimitive](hint) {
    return hint === 'number' ? 100 : '100 元';
  },
  get [Symbol.toStringTag]() {
    return 'Money';
  },
};
console.log(+money); // 100
console.log(`${money}`); // 100 元
console.log(Object.prototype.toString.call(money)); // [object Money]

a 和 b 描述都是 'id',为什么不相等?描述只是方便调试时看,不参与比较。MDN 写明每次 Symbol() 调用都保证返回唯一的 Symbol。Symbol.for 不同,它先查全局 Symbol 注册表,有这个键就返回已有的,没有才新建,所以两次拿到的是同一个。

user 有两个属性,Object.keys 和 JSON.stringify 却只看到 name。这正是 Symbol 键的用处:给对象附加的信息不会混进遍历、序列化这些常规操作里,别人的代码基本不会碰到。

内置 Symbol 在做什么

后半段的 money 对象实现了两个内置 Symbol。+money 需要数字,引擎调用 [Symbol.toPrimitive]('number'),得到 100;模板字符串需要字符串,传入的提示是 'string',得到 '100 元'。Object.prototype.toString 会读 Symbol.toStringTag,所以输出 [object Money],这也是为什么用它判断类型不是绝对可靠,见《怎样准确判断一个值的类型?typeof、instanceof 和 Object.prototype.toString 各有什么局限?》。

最常用的内置 Symbol 是 Symbol.iterator,数组、Map、Set、字符串能被 for...of 和展开语法使用,都是因为实现了它,见《迭代器、可迭代对象和 Generator 有什么关系?》。异步迭代对应的是 Symbol.asyncIterator。

常见追问

Symbol 能用 new 创建吗?

不能。Symbol 只能当函数调用,new Symbol() 会抛 TypeError。它返回的是原始值,typeof 结果是 'symbol'。

Symbol 能隐式转成字符串吗?

不能,'' + Symbol('a') 会抛 TypeError,防止不小心把它当字符串键拼接。要显式转换,用 String(sym) 或 sym.toString(),或者读 sym.description 拿描述。