第 63 期 - JavaScript 反射、元编程与 Symbol 的深度探索
摘要
本文先介绍 JavaScript 中的反射(Reflection)和元编程(Metaprogramming)概念,包括反射中的 Reflect 对象操作和元编程中的 Proxy 用法,然后阐述 Symbol 的特性、用法,最后讲解如何将反射、元编程与 Symbol 结合以创建更灵活强大的代码。
JavaScript 中的反射(Reflection)
反射是指程序能在运行时检查自身结构的能力。JavaScript 中的 Reflect 对象提供了一系列反射方法。
- 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 是实现元编程的关键工具。
- 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 引入的原始数据类型,具有唯一性。
- 使用 Symbol 作为私有属性:
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'
- 使用 Symbol 实现元编程:
- Symbol.iterator 与自定义迭代器:
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 的结合
- 使用 Proxy 拦截 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 版权所有