定义
Symbol是ES6引入的一种原始数据类型(基本类型),是六大数据类型(string,boolean,numeric(number,bigint),null,undefined,symbol)之一,表示唯一的值。
意思是每次调用Symbol(),无论参数是什么,都会返回一个全新的唯一Symbol值。
如:
const sym1 = Symbol(42)
conosle.log(typeof sym1) // symbol
console.log(Symbol(42)) // Symbol(42)
console.log(Symbol(42) === Symbol(42)) // false
不是?为什么Symbol(42) === Symbol(42)是false?????
自增计数器
因为JS引擎内部维护一个自增计数器(或全局唯一ID生成器),会在调用Symbol()时为每个Symbol生成一个新的唯一ID,这个ID与Symbol绑定。伪代码实现:
let symbolId = 0;
function Symbol(description) {
// 每次都生成一个新的唯一ID
const uid = ++symbolId;
// 返回一个带有唯一ID的 Symbol 对象
return { __desc: description, __uid: uid };
}
以V8引擎为例,V8(Chorme/Node.js的JS引擎)内部有一个SymbolTable,每次创建Symbol都会分配一个新的唯一标识(类似指针或ID),这个唯一标识在整个运行时内都不会重复。
属性
Symbol.for(key)
这个属性会在全局注册表中查找/创建Symbol,返回一个全局唯一、可复用的Symbol。
const a = Symbol.for('bar)
const b = Symbol.for('bar')
console.log(a === b) // true
Symbol.keyFor()
用于反查,给定一个通过Symbol.for创建的Symbol,返回它在全局注册中的key(字符串),如果不是全局注册表里的Symbol,返回undefined。
const s1 = Symbol.for('hello')
const s2 = Symbol('world')
const s3 = Symbol.for('hello')
console.log(Symbol.keyFor(s1)) // 'hello'
console.log(Symbol.keyFor(s2)) // undefined
console.log(Symbol.keyFor(s3)) // 'hello'
全局注册表中不存在s2这个共享的symbol,所以取不出来对应的key,可见Symbol.keyFor是获取对应的key的。
全局注册表
全局注册表是JavaScript引擎内部维护的一个全局唯一的对象(类似全局字典),专门用来存储通过Symbol.for(key)创建的Symbol,让不同作用域、不同文件、不同模块之间可以通过同一个key访问到同一个Symbol值。
为什么需要全局注册表?
普通的Symbol()每次都是新值,无法共享,而且在有些场景下(比如跨文件、跨库、跨模块),我们希望通过同一个“名字”拿到同一个Symbol,这就需要全局注册表。
全局注册表的内部原理
其实本质上是一个全局对象,以字符串为key。以Symbol为value,只要key一样,Symbol.for返回的就是同一个Symbol,这个注册表是全局唯一的,所有代码全局共享。
伪代码实现
// 引擎内部
const globalSymbolRegistry = {};
function Symbol_for(key) {
if (globalSymbolRegistry[key]) {
return globalSymbolRegistry[key];
} else {
const newSymbol = Symbol(key);
globalSymbolRegistry[key] = newSymbol;
return newSymbol;
}
}