【前端面试小册】JS-第3节:Symbol详解——唯一标识与元编程基础
一、为什么需要 Symbol(问题)
在 ES5 中,对象属性名只能是字符串,容易发生命名冲突:后定义的属性会覆盖先定义的属性。大型工程或多库组合时尤为常见。
- 痛点:如何为对象添加“绝不会冲突”的键?
- 答案:使用 Symbol 作为键。每个 Symbol 都是唯一的,不会与任何其他键相等。
二、Symbol 是什么(原理)
Symbol是一种原始数据类型,表示独一无二的值。- 通过工厂函数创建:
Symbol([description])。描述只是调试用标签,不参与相等比较。 - 相同描述创建的两个
Symbol也不相等。
const s1 = Symbol();
const s2 = Symbol();
console.log(s1 === s2); // false
const a = Symbol('tag');
const b = Symbol('tag');
console.log(a === b); // false
三、如何使用 Symbol(实例)
3.1 作为“私有”或不易被枚举到的键
const ID = Symbol('id');
const obj = { name: 'Alice', [ID]: 1001 };
console.log(Object.keys(obj)); // ['name']
console.log(Object.getOwnPropertyNames(obj)); // ['name']
console.log(Object.getOwnPropertySymbols(obj)); // [ Symbol(id) ]
console.log(obj[ID]); // 1001
Object.keys、for...in、JSON.stringify会忽略 Symbol 键。- 可用
Object.getOwnPropertySymbols或Reflect.ownKeys访问。
3.2 避免属性名冲突
const T1 = Symbol('test');
const T2 = Symbol('test');
const obj2 = { [T1]: 'A', [T2]: 'B' }; // 两个不同键,不会互相覆盖
3.3 全局注册表:Symbol.for / Symbol.keyFor
Symbol.for(key):在全局注册表中以key为索引复用同一个 Symbol。Symbol.keyFor(sym):取回通过Symbol.for创建的 Symbol 的 key。
const s1 = Symbol.for('channel');
const s2 = Symbol.for('channel');
console.log(s1 === s2); // true
console.log(Symbol.keyFor(s1)); // 'channel'
console.log(Symbol.keyFor(Symbol('channel'))); // undefined(非全局注册)
四、与 JSON、遍历的交互(实例)
const S = Symbol('s');
const data = { [S]: 'secret', visible: 1 };
console.log(JSON.stringify(data)); // '{"visible":1}'(忽略 Symbol 键和值)
for (const k in data) console.log(k); // 只输出 'visible'
console.log(Reflect.ownKeys(data)); // ['visible', Symbol(s)]
五、内置 Symbol 与元编程(进阶实例)
常用内置 Symbol:
Symbol.iterator:定义可迭代行为Symbol.toStringTag:自定义Object.prototype.toString.call的标识Symbol.hasInstance:自定义instanceof行为Symbol.toPrimitive:自定义类型转换
// 1) 迭代器
const bag = {
items: [1, 2, 3],
[Symbol.iterator]() {
let i = 0;
const arr = this.items;
return {
next() {
return i < arr.length ? { value: arr[i++], done: false } : { done: true };
}
};
}
};
console.log([...bag]); // [1, 2, 3]
// 2) 自定义类型标识
const user = { name: 'Ada', [Symbol.toStringTag]: 'User' };
console.log(Object.prototype.toString.call(user)); // [object User]
// 3) 自定义 instanceof 行为
class Range {}
Range[Symbol.hasInstance] = (instance) => Array.isArray(instance);
console.log([] instanceof Range); // true(自定义)
// 4) 自定义原始值转换
const price = {
amount: 1999,
currency: 'CNY',
[Symbol.toPrimitive](hint) {
if (hint === 'number') return this.amount;
if (hint === 'string') return `${this.amount / 100} ${this.currency}`;
return this.amount;
}
};
console.log(+price); // 1999
console.log(String(price)); // '19.99 CNY'
六、工程实践与注意事项(应用场景)
- 库间防冲突:发布库时以 Symbol 作为内部标识键,避免被用户代码覆盖。
- 弱可见“私有”属性:不持有 Symbol 引用则难以访问,但并非真正私有(可反射取到)。
- 跨作用域共享:使用
Symbol.for在不同 bundle/iframe 间共享同一标识。 - 调试友好:为 Symbol 添加描述,提高可读性:
Symbol('cache#lru')。
七、流程图(创建与查找全局 Symbol)
flowchart TD
A[Symbol.for(key)] -->|查找| B{全局注册表中是否存在 key}
B -- 是 --> C[返回已存在的 Symbol]
B -- 否 --> D[创建新 Symbol 并注册]
C --> E[使用 Symbol]
D --> E[使用 Symbol]
八、面试高频题与易错点
Symbol('x') !== Symbol('x');只有Symbol.for('x')才会复用。JSON.stringify会忽略 Symbol;如需序列化,需自定义逻辑。Object.keys/for...in不包含 Symbol 键,Reflect.ownKeys包含。- 内置 Symbol 常用于框架底层协议(迭代、类型标记、instanceof 定制等)。
九、小结
- Symbol 提供“永不冲突”的属性键,是大型工程与库设计的基石之一。
- 结合内置 Symbol,可实现强大的元编程能力。
前端面试小册 文章被收录于专栏
每天3-4节,跟着我50天学完,一起上岸! 靠它,我拿下阿里/百度/滴滴/字节/快手/腾讯/银行等offer 上岸银行,作为面试官使用,跳槽复习使用的小册
查看19道真题和解析