author: 专注前端开发,分享JavaScript干货
title: JavaScript进阶④|Symbol与元编程,对象的隐藏身份
update: 2026-04-28
tags: JavaScript,Symbol,元编程,唯一标识,内置Symbol,前端进阶
作者:专注前端开发,分享JavaScript干货
更新时间:2026年4月
适合人群:掌握ES6基础,想了解Symbol和元编程的开发者
前言:Symbol是什么?
Symbol 是 ES6 引入的一种新的原始数据类型,表示唯一的值。
元编程 是指操作程序本身(对象、类、函数)的编程方式。
一、Symbol 基础
1.1 创建 Symbol
javascript
// 创建 Symbol(可选描述符)
const sym1 = Symbol();
const sym2 = Symbol("描述符");
const sym3 = Symbol("描述符");
console.log(sym1); // Symbol()
console.log(sym2); // Symbol(描述符)
console.log(sym2 === sym3); // false(每个Symbol都是唯一的)
// 描述符只是用于调试,不影响唯一性
console.log(sym2.toString()); // "Symbol(描述符)"
console.log(sym2.description); // "描述符"(ES2019+)
1.2 Symbol 作为对象属性键
javascript
const id = Symbol("id");
const user = {
name: "张三",
[id]: 12345 // Symbol 作为属性键
};
console.log(user.name); // "张三"
console.log(user[id]); // 12345
// 注意:Symbol 键不会被常规方法枚举到
console.log(Object.keys(user)); // ["name"]
console.log(Object.getOwnPropertyNames(user)); // ["name"]
console.log(Object.getOwnPropertySymbols(user)); // [Symbol(id)]
二、全局 Symbol 注册表
javascript
// Symbol.for(key):从全局注册表获取或创建 Symbol
const sym1 = Symbol.for("app.id"); // 创建并注册
const sym2 = Symbol.for("app.id"); // 从注册表获取
console.log(sym1 === sym2); // true ✅(相同的 key 返回相同的 Symbol)
// Symbol.keyFor(sym):获取全局 Symbol 的 key
console.log(Symbol.keyFor(sym1)); // "app.id"
console.log(Symbol.keyFor(Symbol("test"))); // undefined(非全局 Symbol)
三、内置 Symbol(元编程关键)
JavaScript 内置了许多 Symbol 值,用于改变对象的行为。
3.1 Symbol.iterator(可迭代)
javascript
// 让对象可迭代
const myObj = {
data: [1, 2, 3],
[Symbol.iterator]() {
let index = 0;
return {
next: () => {
if (index < this.data.length) {
return { value: this.data[index++], done: false };
}
return { value: undefined, done: true };
}
};
}
};
for (const item of myObj) {
console.log(item); // 1, 2, 3
}
3.2 Symbol.toPrimitive(类型转换)
javascript
// 控制对象转原始值的行为
const money = {
value: 100,
[Symbol.toPrimitive](hint) {
console.log(`hint: ${hint}`); // "number"、"string" 或 "default"
if (hint === "number") {
return this.value;
}
if (hint === "string") {
return `¥${this.value}`;
}
return this.value;
}
};
console.log(+money); // hint: number → 100
console.log(`${money}`); // hint: string → "¥100"
console.log(money + 10); // hint: default → 110
3.3 Symbol.toStringTag(toString)
javascript
// 改变 Object.prototype.toString.call(obj) 的结果
const myObj = {
[Symbol.toStringTag]: "MyObject"
};
console.log(Object.prototype.toString.call(myObj)); // "[object MyObject]"
// 内置对象的例子
console.log(Object.prototype.toString.call([])); // "[object Array]"
console.log(Object.prototype.toString.call(new Map())); // "[object Map]"
3.4 其他常用内置 Symbol
javascript
// Symbol.hasInstance:instanceof 操作符
class MyClass {
static [Symbol.hasInstance](instance) {
return instance instanceof Array; // 自定义 instanceof 行为
}
}
console.log([] instanceof MyClass); // true
// Symbol.isConcatSpreadable:控制 concat 是否展开
const arr = [1, 2, 3];
arr[Symbol.isConcatSpreadable] = false;
console.log([].concat(arr)); // [[1, 2, 3]](不展开)
// Symbol.species:创建派生对象时使用的构造函数
class MyArray extends Array {
static get [Symbol.species]() {
return Array; // 用 Array 而不是 MyArray 创建新数组
}
}
const myArr = new MyArray(1, 2, 3);
const mapped = myArr.map(x => x * 2);
console.log(mapped instanceof MyArray); // false(是 Array)
四、实战:用 Symbol 实现私有属性
javascript
// 模块内部
const _width = Symbol("width");
const _height = Symbol("height");
class Rectangle {
constructor(width, height) {
this[_width] = width;
this[_height] = height;
}
get area() {
return this[_width] * this[_height];
}
resize(width, height) {
this[_width] = width;
this[_height] = height;
}
}
const rect = new Rectangle(10, 20);
console.log(rect.area); // 200
// 外部无法直接访问私有属性(除非知道 Symbol)
// console.log(rect._width); // undefined ❌
// 但可以通过 Object.getOwnPropertySymbols 获取
const syms = Object.getOwnPropertySymbols(rect);
console.log(rect[syms[0]]); // 10(可以拿到,但很不方便)
五、实战:用 Symbol 做事件发射器(避免冲突)
javascript
// 用 Symbol 作为事件名,避免字符串冲突
const EVENTS = {
CONNECT: Symbol("connect"),
DISCONNECT: Symbol("disconnect"),
MESSAGE: Symbol("message")
};
class EventEmitter {
constructor() {
this.listeners = new Map();
}
on(event, callback) {
if (!this.listeners.has(event)) {
this.listeners.set(event, []);
}
this.listeners.get(event).push(callback);
}
emit(event, ...args) {
const callbacks = this.listeners.get(event);
if (callbacks) {
callbacks.forEach(cb => cb(...args));
}
}
}
const emitter = new EventEmitter();
emitter.on(EVENTS.CONNECT, () => console.log("连接成功"));
emitter.emit(EVENTS.CONNECT); // "连接成功"
六、知识卡
| Symbol 方法/属性 | 说明 |
|---|---|
Symbol() |
创建唯一 Symbol |
Symbol.for(key) |
从全局注册表获取/创建 |
Symbol.keyFor(sym) |
获取全局 Symbol 的 key |
Symbol.iterator |
使对象可迭代 |
Symbol.toPrimitive |
控制类型转换 |
Symbol.toStringTag |
改变 toString 结果 |
Object.getOwnPropertySymbols() |
获取对象的 Symbol 键 |
七、课后作业
- 用 Symbol 实现一个对象的私有方法(外部无法直接调用)
- 用
Symbol.hasInstance自定义一个类的instanceof行为 - 用
Symbol.toPrimitive实现一个可以参与数学运算的货币对象
有问题欢迎评论区留言,大家一起讨论!
标签:JavaScript | Symbol | 元编程 | 唯一标识 | 内置Symbol | 前端进阶