第 63 期 - JavaScript 反射、元编程与 Symbol 的深度探索
logoFRONTALK AI/12月25日 16:31/阅读原文

摘要

本文先介绍 JavaScript 中的反射(Reflection)和元编程(Metaprogramming)概念,包括反射中的 Reflect 对象操作和元编程中的 Proxy 用法,然后阐述 Symbol 的特性、用法,最后讲解如何将反射、元编程与 Symbol 结合以创建更灵活强大的代码。

JavaScript 中的反射(Reflection)

反射是指程序能在运行时检查自身结构的能力。JavaScript 中的 Reflect 对象提供了一系列反射方法。

const spaceship = {
    name: "Apollo",
    speed: 10000,
};
// 获取属性值
console.log(Reflect.get(spaceship, "name")); // 'Apollo'
// 设置属性值
Reflect.set(spaceship, "speed", 20000);
console.log(spaceship.speed); // 20000
// 检查属性是否存在
console.log(Reflect.has(spaceship, "speed")); // true
// 删除属性
Reflect.deleteProperty(spaceship, "speed");
console.log(spaceship.speed); // undefined
function safeDeleteProperty(obj, prop) {
    if (Reflect.has(obj, prop)) {
        return Reflect.deleteProperty(obj, prop);
    }
    return false;
}
const spacecraft = { mission: "Explore Mars" };
console.log(safeDeleteProperty(spacecraft, "mission")); // true
console.log(spacecraft.mission); // undefined
console.log(safeDeleteProperty(spacecraft, "nonExistentProp")); // false
const pilot = {
    name: "Buzz Aldrin",
    fly: function (destination) {
        return `${this.name} is flying to ${destination}!`;
    },
};
const destination = "Moon";
console.log(Reflect.apply(pilot.fly, pilot, [destination]));
// 'Buzz Aldrin is flying to Moon!'

JavaScript 中的元编程(Metaprogramming)

元编程允许编写操纵其他代码的代码,JavaScript 中的 Proxy 是实现元编程的关键工具。

const target = {
    message1: "Hello",
    message2: "World",
};
const handler = {
    get: function (target, prop, receiver) {
        if (prop === "message1") {
            return "Proxy says Hi!";
        }
        return Reflect.get(...arguments);
    },
};
const proxy = new Proxy(target, handler);
console.log(proxy.message1); // 'Proxy says Hi!'
console.log(proxy.message2); // 'World'
const userValidator = {
    set: function (target, prop, value) {
        if (prop === "age" && (typeof value!== "number" || value <= 0)) {
            throw new Error("Age must be a positive number");
        }
        if (prop === "email" &&!value.includes("@")) {
            throw new Error("Invalid email format");
        }
        target[prop] = value;
        return true;
    },
};
const user = new Proxy({}, userValidator);
try {
    user.age = 25; // 成功
    user.email = "example@domain.com"; // 成功
    user.age = -5; // 抛出错误
} catch (error) {
    console.error(error.message);
}
try {
    user.email = "invalid-email"; // 抛出错误
} catch (error) {
    console.error(error.message);
}
const handler = {
    set(target, prop, value) {
        console.log(`Property ${prop} set to ${value}`);
        target[prop] = value;
        return true;
    },
};
const spaceship = new Proxy({ speed: 0 }, handler);
spaceship.speed = 10000; // Console: Property speed set to 10000
spaceship.speed = 20000; // Console: Property speed set to 20000
const secureHandler = {
    deleteProperty(target, prop) {
        throw new Error(`Property ${prop} cannot be deleted`);
    },
    set(target, prop, value) {
        if (prop in target) {
            throw new Error(`Property ${prop} is read - only`);
        }
        target[prop] = value;
        return true;
    },
};
const secureObject = new Proxy({ name: "Secret Document" }, secureHandler);
try {
    delete secureObject.name; // 抛出错误
} catch (error) {
    console.error(error.message);
}
try {
    secureObject.name = "Classified"; // 抛出错误
} catch (error) {
    console.error(error.message);
}

JavaScript 中的 Symbol

Symbol 是 ES6 引入的原始数据类型,具有唯一性。

const privateName = Symbol("name");
class Spaceship {
    constructor(name) {
        this[privateName] = name;
    }
    getName() {
        return this[privateName];
    }
}
const apollo = new Spaceship("Apollo");
console.log(apollo.getName()); // Apollo
console.log(Object.keys(apollo)); // []
console.log(Object.getOwnPropertySymbols(apollo)); // [ Symbol(name) ]
const libraryProp = Symbol("libProperty");
const obj = {
    [libraryProp]: "Library data",
    anotherProp: "Some other data",
};
console.log(obj[libraryProp]); // 'Library data'
const collection = {
    items: ["🚀", "🌕", "🛸"],
    [Symbol.iterator]: function* () {
        for (let item of this.items) {
            yield item;
        }
    },
};
for (let item of collection) {
    console.log(item);
}
// 输出:
// 🚀
// 🌕
// 🛸
- **Symbol.toPrimitive 与类型转换**:
const spaceship = {
    name: "Apollo",
    speed: 10000,
    [Symbol.toPrimitive](hint) {
        switch (hint) {
            case "string":
                return this.name;
            case "number":
                return this.speed;
            default:
                return `Spaceship: ${this.name} traveling at ${this.speed} km/h`;
        }
    },
};
console.log(`${spaceship}`); // Apollo
console.log(+ spaceship); // 10000
console.log(spaceship + ""); // Spaceship: Apollo traveling at 10000 km/h

反射、元编程与 Symbol 的结合

const secretSymbol = Symbol("secret");
const spaceship = {
    name: "Apollo",
    [secretSymbol]: "Classified data",
};
const handler = {
    get: function (target, prop, receiver) {
        if (prop === secretSymbol) {
            return "Access Denied!";
        }
        return Reflect.get(...arguments);
    },
};
const proxy = new Proxy(spaceship, handler);
console.log(proxy.name); // Apollo
console.log(proxy[secretSymbol]); // Access Denied!
const validateSymbol = Symbol("validate");
const handler = {
    set(target, prop, value) {
        if (prop === validateSymbol) {
            if (typeof value!== "string" || value.length < 5) {
                throw new Error(
                    "Validation failed: String length must be at least 5 characters"
                );
            }
        }
        target[prop] = value;
        return true;
    },
};
const spaceship = new Proxy({}, handler);
try {
    spaceship[validateSymbol] = "abc"; // 抛出错误
} catch (error) {
    console.error(error.message); // Validation failed: String length must be at least 5 characters
}
spaceship[validateSymbol] = "Apollo"; // 成功
 

扩展阅读

Made by 捣鼓键盘的小麦 / © 2025 Front Talk 版权所有