JavaScript Symbol与Well-Known Symbols—定制语言内置行为|新宇宙博客console
log
description
const
SECRET
Symbol
'secret'
const
SECRET
'hidden value'
public
'visible'
console
log
Object
keys
console
log
Object
getOwnPropertySymbols
console
log
JSON
stringify
console
log
SECRET
2. Symbol.iterator 与迭代协议
class Range {
constructor(start, end, step = 1) {
this.start = start;
this.end = end;
this.step = step;
}
[Symbol.iterator]() {
let current = this.start;
const { end, step } = this;
return {
next() {
if (current <= end) {
const value = current;
current += step;
return { value, done: false };
}
return { done: true };
},
return(value) {
console.log('Iterator closed early');
return { value, done: true };
}
};
}
}
const range = new Range(1, 10, 2);
for (const n of range) {
console.log(n);
}
console.log([...range]);
3. Symbol.toPrimitive 与类型转换
class Temperature {
#celsius;
constructor(celsius) {
this.#celsius = celsius;
}
[Symbol.toPrimitive](hint) {
switch (hint) {
case 'number':
return this.#celsius;
case 'string':
return `${this.#celsius}°C`;
default:
return this.#celsius;
}
}
}
const temp = new Temperature(36.6);
console.log(+temp);
console.log(`${temp}`);
console.log(temp + 0);
console.log(temp > 30);
4. Symbol.hasInstance 与 instanceof
class Validator {
static [Symbol.hasInstance](instance) {
return instance !== null &&
typeof instance === 'object' &&
typeof instance.validate === 'function';
}
}
const form = {
validate() { return true; }
};
console.log(form instanceof Validator);
console.log({} instanceof Validator);
5. Symbol.species 与派生类
class MyArray extends Array {
static get [Symbol.species]() {
return Array;
}
}
const myArr = new MyArray(1, 2, 3);
const mapped = myArr.map(x => x * 2);
console.log(mapped instanceof MyArray);
console.log(mapped instanceof Array);
6. 其他 Well-Known Symbols
class Database {
get [Symbol.toStringTag]() {
return 'Database';
}
}
console.log(Object.prototype.toString.call(new Database()));
const arrayLike = {
0: 'a', 1: 'b', length: 2,
[Symbol.isConcatSpreadable]: true
};
console.log(['x'].concat(arrayLike));
class CaseInsensitiveMatcher {
constructor(pattern) { this.pattern = pattern.toLowerCase(); }
[Symbol.match](str) {
return str.toLowerCase().includes(this.pattern) ? [this.pattern] : null;
}
}
console.log('Hello World'.match(new CaseInsensitiveMatcher('hello')));
7. Symbol.for 全局注册表
const s1 = Symbol.for('shared.key');
const s2 = Symbol.for('shared.key');
console.log(s1 === s2);
console.log(Symbol.keyFor(s1));
console.log(Symbol.keyFor(Symbol('local')));
8. 实战案例
实战案例 1:类型安全的枚举
const Direction = Object.freeze({
UP: Symbol('UP'),
DOWN: Symbol('DOWN'),
LEFT: Symbol('LEFT'),
RIGHT: Symbol('RIGHT'),
[Symbol.iterator]() {
return [this.UP, this.DOWN, this.LEFT, this.RIGHT][Symbol.iterator]();
}
});
function move(direction) {
if (![...Direction].includes(direction)) {
throw new TypeError('Invalid direction');
}
}
move(Direction.UP);
move('UP');
实战案例 2:可释放资源协议 (Symbol.dispose)
class FileHandle {
#handle;
constructor(path) {
this.#handle = openFile(path);
}
[Symbol.dispose]() {
this.#handle.close();
console.log('File closed');
}
read() { return this.#handle.readAll(); }
}
{
using file = new FileHandle('/tmp/data.txt');
const content = file.read();
}
实战案例 3:私有协议标记
const INTERNAL = Symbol('internal');
class Plugin {
[INTERNAL] = { initialized: false };
init() {
this[INTERNAL].initialized = true;
}
static getInternals(plugin) {
return plugin[INTERNAL];
}
}
const plugin = new Plugin();
console.log(Object.keys(plugin));
9. 深度追问
Q1:Symbol 能被 GC 吗?
Symbol() 创建的 Symbol 在无引用时可被 GC。但 Symbol.for() 创建的 Symbol 存在于全局注册表中,永远不会被 GC,类似于字符串常量池。
Q2:为什么 Symbol 不能用 new?
因为 Symbol 是原始类型,不是对象。如果允许 new Symbol(),返回的将是 Symbol 包装对象,这与设计意图矛盾。需要包装对象时使用 Object(Symbol())。
Q3:WeakMap 为什么不能用 Symbol 作为键?
ES2023 已经允许了!Symbol 可以作为 WeakMap 的键,但仅限于非注册的 Symbol(不是 Symbol.for() 创建的),因为注册 Symbol 永远不会被 GC。
10. 总结表格
| Well-Known Symbol | 功能 | 触发操作 |
|---|
Symbol.iterator | 定义迭代行为 | for...of, ...spread |
Symbol.asyncIterator | 异步迭代 | for await...of |
Symbol.toPrimitive | 类型转换 | +obj, \${obj}`` |
Symbol.hasInstance | instanceof 检测 | x instanceof Class |
Symbol.species | 派生构造器 | arr.map() 返回类型 |
Symbol.toStringTag | 对象标签 | Object.prototype.toString |
Symbol.isConcatSpreadable | 数组展开 | [].concat(obj) |
Symbol.match/replace/search/split | 正则协议 | str.match(obj) |
Symbol.dispose | 资源释放 | using 声明 |