javascript
Proxy、Reflect 与 Symbol
By AI-Writer 18 min read
JavaScript 提供了多种元编程(Meta-programming)能力,允许代码在运行时检查和修改语言行为。Proxy、Reflect 和 Symbol 是实现高级元编程的三大核心工具。
Proxy —— 对象的拦截器
Proxy 允许你创建一个对象的代理,拦截并自定义该对象的基本操作(如属性读取、赋值、函数调用等)。
基本语法
javascript
const target = { name: 'Alice', age: 28 };
const proxy = new Proxy(target, {
// 拦截属性读取
get(target, prop, receiver) {
if (prop === 'age') {
return `${target[prop]} 岁`;
}
return Reflect.get(target, prop, receiver);
},
// 拦截属性赋值
set(target, prop, value, receiver) {
if (prop === 'age' && typeof value !== 'number') {
throw new TypeError('age 必须是数字');
}
return Reflect.set(target, prop, value, receiver);
}
});
console.log(proxy.name); // 'Alice'
console.log(proxy.age); // '28 岁'
proxy.age = 30; // OK
// proxy.age = 'thirty'; // TypeError!常用拦截器
javascript
const handler = {
// 属性读取:obj.prop 或 obj[prop]
get(target, prop, receiver) { /* ... */ },
// 属性赋值:obj.prop = value
set(target, prop, value, receiver) { /* ... */ },
// 属性删除:delete obj.prop
deleteProperty(target, prop) { /* ... */ },
// in 运算符:prop in obj
has(target, prop) { /* ... */ },
// Object.keys / for...in
ownKeys(target) { /* ... */ },
// 获取属性描述符
getOwnPropertyDescriptor(target, prop) { /* ... */ },
// 定义属性
defineProperty(target, prop, descriptor) { /* ... */ },
// 函数调用:proxy(...args)
apply(target, thisArg, args) { /* ... */ },
// new 操作符:new proxy(...)
construct(target, args, newTarget) { /* ... */ }
};实战:响应式对象
javascript
function reactive(obj, onChange) {
return new Proxy(obj, {
set(target, prop, value, receiver) {
const oldValue = target[prop];
const result = Reflect.set(target, prop, value, receiver);
if (oldValue !== value) {
onChange(prop, value, oldValue);
}
return result;
}
});
}
const state = reactive({ count: 0 }, (key, val, old) => {
console.log(`${key}: ${old} → ${val}`);
});
state.count = 1; // "count: 0 → 1"
state.count = 2; // "count: 1 → 2"实战:私有属性保护
javascript
function withPrivate(obj, ...privateKeys) {
return new Proxy(obj, {
get(target, prop) {
if (privateKeys.includes(prop)) {
throw new Error(`'${prop}' 是私有属性,无法访问`);
}
return target[prop];
}
});
}
const user = withPrivate({ name: 'Alice', password: 'secret123' }, 'password');
console.log(user.name); // 'Alice'
// console.log(user.password); // Error!实战:函数参数校验
javascript
function validate(fn, schema) {
return new Proxy(fn, {
apply(target, thisArg, args) {
for (let i = 0; i < schema.length; i++) {
if (typeof args[i] !== schema[i]) {
throw new TypeError(
`参数 ${i} 期望类型 ${schema[i]},实际得到 ${typeof args[i]}`
);
}
}
return target.apply(thisArg, args);
}
});
}
const add = validate((a, b) => a + b, ['number', 'number']);
console.log(add(2, 3)); // 5
// add('2', 3); // TypeError!Reflect —— 操作对象的统一 API
Reflect 提供了一组与 Proxy 拦截器对应的方法,用于以函数式方式执行对象操作。它的设计目标是让对象操作更加规范化。
Reflect 与直接操作对比
javascript
const obj = { x: 1 };
// 传统写法 vs Reflect 写法
obj.x = 2; // 赋值
Reflect.set(obj, 'x', 2); // 等价的 Reflect 写法
'x' in obj; // in 运算符
Reflect.has(obj, 'x'); // 等价的 Reflect 写法
delete obj.x; // 删除属性
Reflect.deleteProperty(obj, 'x'); // 等价的 Reflect 写法
Object.defineProperty(obj, 'y', { value: 3 });
Reflect.defineProperty(obj, 'y', { value: 3 });为什么使用 Reflect?
- 更合理的返回值:
javascript
// Object.defineProperty 成功时返回对象(不太直观)
const result1 = Object.defineProperty(obj, 'a', { value: 1 });
// Reflect.defineProperty 成功时返回 boolean
const success = Reflect.defineProperty(obj, 'b', { value: 2 });- 函数式调用:
javascript
const methods = [Reflect.get, Reflect.set, Reflect.has];
const target = { foo: 'bar' };
// 可以像普通函数一样传递
methods[0](target, 'foo'); // 'bar'- receiver 参数正确处理 this:
javascript
const parent = {
name: 'Parent',
greet() {
return `Hello from ${this.name}`;
}
};
const child = Object.create(parent);
child.name = 'Child';
// 使用 Reflect.get 的 receiver 参数确保 this 指向正确
Reflect.get(parent, 'greet', child)(); // "Hello from Child"Proxy + Reflect 最佳实践
在 Proxy handler 中使用 Reflect 是推荐模式:
javascript
const proxy = new Proxy(target, {
get(target, prop, receiver) {
console.log(`读取: ${String(prop)}`);
// 默认行为委托给 Reflect
return Reflect.get(target, prop, receiver);
},
set(target, prop, value, receiver) {
console.log(`设置: ${String(prop)} = ${value}`);
return Reflect.set(target, prop, value, receiver);
}
});Symbol —— 唯一的原始值
Symbol 是 ES6 引入的一种原始数据类型,每个 Symbol 值都是唯一的,不会与其他任何值相等。
创建 Symbol
javascript
const s1 = Symbol('description');
const s2 = Symbol('description');
console.log(s1 === s2); // false —— 每个 Symbol 都是唯一的
// Symbol.for 从全局注册表获取/创建
const a = Symbol.for('app.id');
const b = Symbol.for('app.id');
console.log(a === b); // true —— 同一个注册表键
// Symbol.keyFor 获取注册表键
console.log(Symbol.keyFor(a)); // 'app.id'用作对象键
Symbol 作为对象键时不会被常规方法枚举到,适合创建”私有”属性:
javascript
const id = Symbol('id');
const secret = Symbol('secret');
const user = {
name: 'Alice',
[id]: 12345,
[secret]: 'hidden-value'
};
// 常规枚举方法无法访问 Symbol 键
console.log(Object.keys(user)); // ['name']
console.log(Object.getOwnPropertyNames(user)); // ['name']
// 需要专门的方法获取 Symbol 键
console.log(Object.getOwnPropertySymbols(user)); // [Symbol(id), Symbol(secret)]
console.log(Reflect.ownKeys(user)); // ['name', Symbol(id), Symbol(secret)]Well-Known Symbols
JavaScript 内置了一系列知名 Symbol(Well-Known Symbols),用于自定义语言内部行为:
javascript
// Symbol.iterator —— 定义对象的默认迭代器
const range = {
from: 1,
to: 5,
[Symbol.iterator]() {
return {
current: this.from,
last: this.to,
next() {
return this.current <= this.last
? { done: false, value: this.current++ }
: { done: true };
}
};
}
};
console.log([...range]); // [1, 2, 3, 4, 5]
// Symbol.toPrimitive —— 控制对象转为原始值的逻辑
const money = {
amount: 100,
currency: 'CNY',
[Symbol.toPrimitive](hint) {
if (hint === 'string') return `${this.amount} ${this.currency}`;
if (hint === 'number') return this.amount;
return `${this.amount}`;
}
};
console.log(String(money)); // "100 CNY"
console.log(Number(money)); // 100
console.log(money + 50); // "10050" 或 150,取决于默认行为其他知名 Symbol
| Symbol | 用途 |
|---|---|
Symbol.iterator | 定义默认迭代器(for…of) |
Symbol.asyncIterator | 定义异步迭代器(for await…of) |
Symbol.toPrimitive | 自定义原始值转换 |
Symbol.toStringTag | 自定义 Object.prototype.toString 标签 |
Symbol.hasInstance | 自定义 instanceof 行为 |
Symbol.isConcatSpreadable | 控制 concat 是否展开 |
Symbol.species | 指定派生对象的构造函数 |
Symbol.match / Symbol.replace / Symbol.search / Symbol.split | 自定义字符串方法行为 |
实战:自定义 toString 标签
javascript
class MyClass {
[Symbol.toStringTag] = 'MyClass';
}
const obj = new MyClass();
console.log(Object.prototype.toString.call(obj)); // "[object MyClass]"三者协作的完整示例
javascript
// 创建一个可观测的集合类
class ObservableArray extends Array {
#listeners = new Set();
constructor(...args) {
super(...args);
// 返回 Proxy 拦截数组操作
return new Proxy(this, {
set(target, prop, value, receiver) {
const oldLen = target.length;
const result = Reflect.set(target, prop, value, receiver);
// 通知监听器
receiver[Symbol.for('observable.notify')]({
type: 'set',
index: prop,
value
});
return result;
}
});
}
subscribe(fn) {
this.#listeners.add(fn);
return () => this.#listeners.delete(fn);
}
[Symbol.for('observable.notify')](event) {
this.#listeners.forEach(fn => fn(event));
}
// 自定义迭代行为
*[Symbol.iterator]() {
for (let i = 0; i < this.length; i++) {
yield { index: i, value: this[i] };
}
}
}
const arr = new ObservableArray(1, 2, 3);
const unsubscribe = arr.subscribe(event => {
console.log('变化:', event);
});
arr.push(4); // 触发通知小结
| 特性 | 作用 | 典型场景 |
|---|---|---|
| Proxy | 拦截对象操作 | 响应式系统、校验、日志 |
| Reflect | 规范化对象操作 | Proxy handler 中委托默认行为 |
| Symbol | 创建唯一标识 | 私有属性、自定义语言行为 |
Proxy、Reflect 和 Symbol 共同构成了 JavaScript 的元编程基础设施。掌握它们,你不仅能写出更强大的代码,还能深入理解 Vue 3 的响应式系统、迭代协议等高级机制的实现原理。
#javascript
#proxy
#reflect
#symbol
#元编程
评论
A
Written by
AI-Writer
Related Articles
javascript
#7 Promise 与 async/await
深入理解 JavaScript 异步编程核心——Promise 状态机、组合 API 与 async/await 语法糖,掌握错误处理与事件循环的协作关系。
Read More javascript
#11 数组内置方法综合运用
深入掌握 forEach、map、filter、reduce、find、sort 等数组方法的组合技巧,避开常见性能陷阱,写出更高效的函数式代码。
Read More