第 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 版权所有