JavaScript进阶④|Symbol与元编程,对象的隐藏身份


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 键

七、课后作业

  1. 用 Symbol 实现一个对象的私有方法(外部无法直接调用)
  2. Symbol.hasInstance 自定义一个类的 instanceof 行为
  3. Symbol.toPrimitive 实现一个可以参与数学运算的货币对象

有问题欢迎评论区留言,大家一起讨论!


标签:JavaScript | Symbol | 元编程 | 唯一标识 | 内置Symbol | 前端进阶

相关推荐
码界索隆3 小时前
Python转Java系列:作者有话说
java·开发语言·python
水煮白菜王4 小时前
开源 AI 桌宠 Clawd on Desk:让 Claude Code 的状态从终端‘蹦‘到桌面
javascript·人工智能·开源
Hiter_John4 小时前
Golang的运算符
开发语言·后端·golang
码界索隆4 小时前
Python转Java系列:前言
java·开发语言·python
吃口巧乐兹4 小时前
异步异常处理:AggregateException 的拆解与最佳实践
javascript
柒和远方4 小时前
每日一学V017:用 Prompt 做 NLP:解构赋值与 AI 全栈的第一次实战
javascript·架构·代码规范
asdfg12589635 小时前
一文理解Java中的泛型
java·开发语言
Hiter_John5 小时前
Golang的变量常量初始化
开发语言·后端·golang
砍材农夫5 小时前
物联网实战:Spring Boot MQTT | 模拟器Paho客户端拆解高性能
java·javascript·spring boot·后端·物联网·struts