JavaScript Proxy与Reflect
在 JavaScript 中如何拦截对象的读取、修改、删除操作?Vue3的响应式系统如何实现?本文将深入讲解 JavaScript 的元编程世界,探索 Proxy 和 Reflect 的奥秘。
前言:从Vue3的响应式说起
// Vue3的响应式数据实现原理
const reactive = (target) => {
return new Proxy(target, {
get(target, key, receiver) {
track(target, key); // 依赖收集
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
trigger(target, key); // 触发更新
return result;
}
});
};
Vue3的响应式数据实现原理的背后,就是 Proxy 和 Reflect 。理解它们,我们就能真正掌握 JavaScript 元编程的能力。
理解Proxy与Reflect
什么是Proxy(代理)?
Proxy 对象用于创建一个对象的代理,从而实现对基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。
Proxy工作流程示意图
┌─────────┐ 操作请求 ┌─────────┐ 转发操作 ┌─────────┐
│ 客户端 │───────────→ │ Proxy │───────────→ │ 目标对象 │
│ │ │ 代理 │ │ │
│ │←─────────── │ │←─────────── │ │
└─────────┘ 响应结果 └─────────┘ 实际结果 └─────────┘
↓ ↓
┌─────────────┐ ┌─────────────┐
│ 拦截和自定义 │ │ 原始行为 │
│ (陷阱) │ │ │
└─────────────┘ └─────────────┘
什么是Reflect(反射)?
Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与 Proxy 的陷阱方法一一对应。
为什么需要Reflect?
- 统一的操作API
- 更好的错误处理(返回布尔值而非抛出异常)
- 与Proxy陷阱方法一一对应
代理基础:创建第一个代理
// 目标对象
const target = {
name: '张三',
age: 30
};
// 处理器对象(包含陷阱方法)
const handler = {
// 拦截属性读取
get(target, property, receiver) {
// 添加自定义逻辑
if (property === 'age') {
return `${target[property]}岁`;
}
// 默认行为:使用Reflect
return Reflect.get(target, property, receiver);
},
// 拦截属性设置
set(target, property, value, receiver) {
// 添加验证逻辑
if (property === 'age' && (value < 0 || value > 150)) {
console.warn('年龄必须在0-150之间');
return false; // 返回false表示设置失败
}
// 默认行为:使用Reflect
return Reflect.set(target, property, value, receiver);
}
};
// 创建代理
const proxy = new Proxy(target, handler);
console.log(proxy.name); // 张三
console.log(proxy.age); // 30岁
console.log(target.age); // 30
13种可拦截的陷阱方法
完整陷阱方法概览
const completeHandler = {
// 1. 属性访问拦截
get(target, prop, receiver) {
console.log(`get: ${prop}`);
return Reflect.get(target, prop, receiver);
},
// 2. 属性赋值拦截
set(target, prop, value, receiver) {
console.log(`set: ${prop} = ${value}`);
return Reflect.set(target, prop, value, receiver);
},
// 3. in操作符拦截
has(target, prop) {
console.log(`has: ${prop}`);
return Reflect.has(target, prop);
},
// 4. delete操作符拦截
deleteProperty(target, prop) {
console.log(`delete: ${prop}`);
return Reflect.deleteProperty(target, prop);
},
// 5. 构造函数调用拦截
construct(target, args, newTarget) {
console.log(`construct with args:`, args);
return Reflect.construct(target, args, newTarget);
},
// 6. 函数调用拦截
apply(target, thisArg, args) {
console.log(`apply: thisArg=`, thisArg, 'args=', args);
return Reflect.apply(target, thisArg, args);
},
// 7. Object.getOwnPropertyDescriptor拦截
getOwnPropertyDescriptor(target, prop) {
console.log(`getOwnPropertyDescriptor: ${prop}`);
return Reflect.getOwnPropertyDescriptor(target, prop);
},
// 8. Object.defineProperty拦截
defineProperty(target, prop, descriptor) {
console.log(`defineProperty: ${prop}`, descriptor);
return Reflect.defineProperty(target, prop, descriptor);
},
// 9. 原型访问拦截
getPrototypeOf(target) {
console.log('getPrototypeOf');
return Reflect.getPrototypeOf(target);
},
// 10. 原型设置拦截
setPrototypeOf(target, prototype) {
console.log(`setPrototypeOf:`, prototype);
return Reflect.setPrototypeOf(target, prototype);
},
// 11. 可扩展性判断拦截
isExtensible(target) {
console.log('isExtensible');
return Reflect.isExtensible(target);
},
// 12. 防止扩展拦截
preventExtensions(target) {
console.log('preventExtensions');
return Reflect.preventExtensions(target);
},
// 13. 自身属性枚举拦截
ownKeys(target) {
console.log('ownKeys');
return Reflect.ownKeys(target);
}
};
陷阱方法详细解析
1. get陷阱:属性访问拦截
get() 会在获取属性值的操作中被调用:
const getHandler = {
get(target, property, receiver) {
// 处理不存在的属性
if (!(property in target)) {
throw new Error(`属性不存在: ${property}`);
}
// 处理私有属性(约定以_开头的属性为私有)
if (property.startsWith('_')) {
throw new Error(`不能访问私有属性: ${property}`);
}
return Reflect.get(target, property, receiver);
}
};
- 参数说明:
- target:目标对象
- property:引用的目标对象上的属性
- receiver:代理对象
- 返回值说明:返回值无限制
2. set陷阱:属性赋值拦截
set() 会在设置属性值的操作中被调用:
const setHandler = {
set(target, property, value, receiver) {
// 只读属性检查(约定以$开头的属性为只读)
if (property.startsWith('$')) {
throw new Error(`属性不可修改: ${property}`);
}
// 执行设置
return Reflect.set(target, property, value, receiver);
}
};
- 参数说明:
- target:目标对象
- property:引用的目标对象上的属性
- value:要给属性设置的值
- receiver:代理对象
- 返回值说明:返回 true 表示成功;返回false表示失败。
3. has陷阱:in操作符拦截
has() 会在 in 操作符中被调用:
const hasHandler = {
has(target, property) {
// 隐藏某些属性(不在in操作中暴露)
const hiddenProperties = ['password', 'token', '_internal'];
if (hiddenProperties.includes(property)) {
console.log(`隐藏属性: ${property}`);
return false;
}
// 虚拟属性(不存在但返回true)
const virtualProperties = ['isAdmin', 'hasAccess'];
if (virtualProperties.includes(property)) {
console.log(`虚拟属性: ${property}`);
return true;
}
return Reflect.has(target, property);
}
};
- 参数说明:
- target:目标对象
- value:要给属性设置的值
- 返回值说明:返回布尔值表示属性是否存在,返回非布尔型会被转成布尔型。
注:Object.keys() 不受 has() 影响,仍会返回所有属性。
4. apply陷阱:函数调用拦截
apply() 会在调用函数时被调用:
const applyHandler = {
apply(target, thisArg, argumentsList) {
// 参数验证
if (target.name === 'calculate') {
if (argumentsList.length < 2) {
throw new Error('calculate函数需要至少2个参数');
}
if (!argumentsList.every(arg => typeof arg === 'number')) {
throw new Error('calculate函数的所有参数必须是数字');
}
}
return Reflect.apply(target, thisArg, argumentsList);
}
};
- 参数说明:
- target:目标对象
- thisArg:调用函数时的this参数
- argumentsList:调用函数时的参数列表
- 返回值说明:返回值无限制
5. construct陷阱:构造函数拦截
construct() 会在调用函数时被调用:
const constructHandler = {
construct(target, argumentsList, newTarget) {
// 单例模式:确保只有一个实例
if (target._instance) {
console.log('返回现有实例');
return target._instance;
}
// 创建实例
const instance = Reflect.construct(target, argumentsList, newTarget);
// 为单例保存实例
if (target.name === 'Singleton') {
target._instance = instance;
}
// 为实例添加额外属性
instance.createdAt = new Date();
instance._id = Math.random().toString(36).substr(2, 9);
console.log('创建新实例,ID:', instance._id);
return instance;
}
};
- 参数说明:
- target:目标构造函数
- argumentsList:传给目标构造函数的参数列表
- newTarget:最初被调用的构造函数
- 返回值说明:必须返回一个对象
6. ownKeys陷阱:影响Object.keys()等遍历
ownKeys() 会在Object.keys()及类似方法中被调用:
const ownKeysHandler = {
ownKeys(target) {
// 过滤掉以_开头的私有属性
const keys = Reflect.ownKeys(target);
return keys.filter(key => {
if (typeof key === 'string') {
return !key.startsWith('_');
}
return true;
});
}
};
- 参数说明:
- target:目标构造函数
- 返回值说明:必须返回一个包含字符串或符号的可枚举对象
7. getOwnPropertyDescriptor陷阱:影响Object.getOwnPropertyDescriptor()
getOwnPropertyDescriptor() 会在Object.getOwnPropertyDescriptor()方法中被调用:
const getOwnPropertyDescriptorHandler = {
getOwnPropertyDescriptor(target, prop) {
// 隐藏私有属性的描述符
if (prop.startsWith('_')) {
return undefined;
}
return Reflect.getOwnPropertyDescriptor(target, prop);
}
};
- 参数说明:
- target:目标构造函数
- prop:引用的目标对象上的字符串属性
- 返回值说明:必须返回对象,或在属性不存在时返回 undefined
8. defineProperty陷阱:拦截Object.defineProperty()
defineProperty() 会在Object.defineProperty()方法中被调用:
const definePropertyHandler = {
defineProperty(target, prop, descriptor) {
// 防止修改只读属性
if (prop === 'id' && target.id) {
console.warn('id属性是只读的');
return false;
}
return Reflect.defineProperty(target, prop, descriptor);
}
};
- 参数说明:
- target:目标构造函数
- prop:引用的目标对象上的字符串属性
- descriptor:包含可选的enumrable、configurable、value、get 和 set 等定义的对象
- 返回值说明:必须返回布尔值,表示属性是否成功定义
9. deleteProperty陷阱:拦截delete操作
deleteProperty() 会在 delete 操作中被调用:
const deletePropertyHandler = {
deleteProperty(target, prop) {
// 防止删除重要属性
const protectedProps = ['id', 'createdAt'];
if (protectedProps.includes(prop)) {
console.warn(`不能删除受保护的属性: ${prop}`);
return false;
}
return Reflect.deleteProperty(target, prop);
}
};
- 参数说明:
- target:目标构造函数
- property:引用的目标对象上的属性
- 返回值说明:必须返回布尔值,表示属性是否被成功删除
10. preventExtensions陷阱:拦截Object.preventExtensions()
preventExtensions() 会在 Object.preventExtensions() 方法中被调用:
const preventExtensionsHandler = {
preventExtensions(target) {
// 在阻止扩展前添加标记
target._frozenAt = new Date();
return Reflect.preventExtensions(target);
}
};
- 参数说明:
- target:目标构造函数
- 返回值说明:必须返回布尔值,表示 target 是否已经不可扩展
11. getPrototypeOf陷阱:拦截Object.getPrototypeOf()
getPrototypeOf() 会在 Object.getPrototypeOf() 方法中被调用:
const getPrototypeOfHandler = {
getPrototypeOf(target) {
return Reflect.getPrototypeOf(target);
}
};
- 参数说明:
- target:目标构造函数
- 返回值说明:必须返回对象或null
12. setPrototypeOf陷阱:拦截Object.setPrototypeOf()
setPrototypeOf() 会在 Object.setPrototypeOf() 方法中被调用:
const setPrototypeOfHandler = {
setPrototypeOf(target, prototype) {
return Reflect.setPrototypeOf(target, prototype);
}
};
- 参数说明:
- target:目标构造函数
- property:引用的目标对象上的属性
- 返回值说明:必须返回布尔值,表示原型赋值是否成功
13. isExtensible陷阱:拦截Object.isExtensible()
isExtensible() 会在 Object.isExtensible() 方法中被调用:
const isExtensibleHandler = {
isExtensible(targete) {
return Reflect.isExtensible(targete);
}
};
- 参数说明:
- target:目标构造函数
- 返回值说明:必须返回布尔值,表示 target 是否可扩展
可撤销代理
什么是可撤销代理?
可撤销代理(Revocable Proxy)是一种特殊的代理,它提供了一个 revoke() 方法,调用该方法后,代理将不再可用,所有对代理的操作都会抛出 TypeError,即:会中断代理对象和目标对象之间的联系。
const target = { name: 'zhangsan' };
const handler = {
get() {
return 'foo';
}
}
// 创建可撤销代理
const { proxy, revoke } = Proxy.revocable(target, handler);
console.log(proxy.name); // 'zhangsan'
// 撤销代理
revoke();
// 代理已失效
try {
console.log(proxy.name); // TypeError: Cannot perform 'get' on a proxy that has been revoked
} catch (error) {
console.log('错误:', error.message);
}
实现响应式数据(Vue3原理)
// 简易版Vue3响应式系统
class ReactiveSystem {
constructor() {
this.targetMap = new WeakMap(); // 存储依赖关系
this.effectStack = []; // 当前正在执行的effect
this.batchQueue = new Set(); // 批量更新队列
this.isBatching = false; // 是否处于批量更新模式
}
// 核心:创建响应式对象
reactive(target) {
return new Proxy(target, {
get(target, key, receiver) {
// 获取原始值
const result = Reflect.get(target, key, receiver);
// 依赖收集
track(target, key);
// 深度响应式(如果值是对象,继续代理)
if (result && typeof result === 'object') {
return reactive(result);
}
return result;
},
set(target, key, value, receiver) {
// 获取旧值
const oldValue = target[key];
// 设置新值
const result = Reflect.set(target, key, value, receiver);
// 触发更新(值确实改变时才触发)
if (oldValue !== value) {
trigger(target, key);
}
return result;
},
// 处理删除操作
deleteProperty(target, key) {
const hasKey = key in target;
const result = Reflect.deleteProperty(target, key);
if (hasKey && result) {
trigger(target, key);
}
return result;
}
});
}
// 依赖收集
track(target, key) {
if (this.effectStack.length === 0) return;
let depsMap = this.targetMap.get(target);
if (!depsMap) {
depsMap = new Map();
this.targetMap.set(target, depsMap);
}
let dep = depsMap.get(key);
if (!dep) {
dep = new Set();
depsMap.set(key, dep);
}
// 收集当前正在执行的effect
const activeEffect = this.effectStack[this.effectStack.length - 1];
if (activeEffect) {
dep.add(activeEffect);
// effect也需要知道哪些依赖收集了它
activeEffect.deps.push(dep);
}
}
// 触发更新
trigger(target, key) {
const depsMap = this.targetMap.get(target);
if (!depsMap) return;
const dep = depsMap.get(key);
if (dep) {
// 如果是批量更新模式,加入队列
if (this.isBatching) {
dep.forEach(effect => this.batchQueue.add(effect));
} else {
// 立即执行所有依赖的effect
dep.forEach(effect => {
if (effect !== this.effectStack[this.effectStack.length - 1]) {
effect.run();
}
});
}
}
}
// 创建effect(副作用)
effect(fn) {
const effect = new ReactiveEffect(fn, this);
effect.run();
// 返回停止函数
const runner = effect.run.bind(effect);
runner.effect = effect;
runner.stop = () => effect.stop();
return runner;
}
// 开始批量更新
batchStart() {
this.isBatching = true;
}
// 结束批量更新
batchEnd() {
this.isBatching = false;
// 执行队列中的所有effect
this.batchQueue.forEach(effect => {
if (effect !== this.effectStack[this.effectStack.length - 1]) {
effect.run();
}
});
this.batchQueue.clear();
}
// computed计算属性
computed(getter) {
let value;
let dirty = true;
const runner = this.effect(() => {
value = getter();
dirty = false;
});
return {
get value() {
if (dirty) {
runner();
}
return value;
}
};
}
// watch侦听器
watch(source, callback, options = {}) {
let getter;
if (typeof source === 'function') {
getter = source;
} else {
getter = () => this.traverse(source);
}
let oldValue;
const job = () => {
const newValue = runner();
callback(newValue, oldValue);
oldValue = newValue;
};
const runner = this.effect(getter, {
lazy: true,
scheduler: job
});
oldValue = runner();
if (options.immediate) {
job();
}
}
// 深度遍历对象(用于watch)
traverse(value, seen = new Set()) {
if (typeof value !== 'object' || value === null || seen.has(value)) {
return value;
}
seen.add(value);
for (const key in value) {
this.traverse(value[key], seen);
}
return value;
}
}
// 响应式effect类
class ReactiveEffect {
constructor(fn, scheduler) {
this.fn = fn;
this.scheduler = scheduler;
this.deps = [];
this.active = true;
}
run() {
if (!this.active) return;
try {
this.scheduler.effectStack.push(this);
return this.fn();
} finally {
this.scheduler.effectStack.pop();
}
}
stop() {
if (this.active) {
// 从所有依赖中移除自己
this.deps.forEach(dep => dep.delete(this));
this.deps.length = 0;
this.active = false;
}
}
}
实现不可变数据(immer.js原理)
// 简易版immer实现
class Immer {
constructor(baseState) {
this.baseState = baseState;
this.draft = this.createDraft(baseState);
this.isModified = false;
}
// 创建草稿(代理)
createDraft(base) {
// 如果不是对象,直接返回
if (typeof base !== 'object' || base === null) {
return base;
}
// 处理数组
if (Array.isArray(base)) {
return this.createArrayDraft(base);
}
// 处理对象
return this.createObjectDraft(base);
}
// 创建对象草稿
createObjectDraft(base) {
const draft = Array.isArray(base) ? [] : {};
// 复制所有属性
for (const key in base) {
if (base.hasOwnProperty(key)) {
draft[key] = this.createDraft(base[key]);
}
}
// 创建代理
return new Proxy(draft, {
get: (target, prop, receiver) => {
// 特殊属性处理
if (prop === '__immer_draft__') return true;
if (prop === '__immer_original__') return base;
return Reflect.get(target, prop, receiver);
},
set: (target, prop, value, receiver) => {
this.isModified = true;
// 如果新值是普通值,直接设置
if (typeof value !== 'object' || value === null) {
return Reflect.set(target, prop, value, receiver);
}
// 如果新值是草稿,获取其最终状态
if (value.__immer_draft__) {
value = this.finalize(value);
}
// 如果新值是对象,创建新的草稿
const newDraft = this.createDraft(value);
return Reflect.set(target, prop, newDraft, receiver);
},
deleteProperty: (target, prop) => {
this.isModified = true;
return Reflect.deleteProperty(target, prop);
}
});
}
// 创建数组草稿(需要特殊处理)
createArrayDraft(base) {
const draft = this.createObjectDraft(base);
// 代理数组方法
const arrayMethods = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'];
arrayMethods.forEach(method => {
const originalMethod = Array.prototype[method];
draft[method] = function (...args) {
this.isModified = true;
return originalMethod.apply(this, args);
}.bind({ isModified: false }); // 这里简化处理
});
return draft;
}
// 获取草稿
getDraft() {
return this.draft;
}
// 完成修改,生成新状态
produce(producer) {
// 执行生产者函数
producer(this.draft);
// 如果没有修改,返回原状态
if (!this.isModified) {
return this.baseState;
}
// 最终化草稿,生成新状态
return this.finalize(this.draft);
}
// 最终化草稿(将草稿转换为普通对象)
finalize(draft) {
// 如果不是草稿,直接返回
if (!draft || !draft.__immer_draft__) {
return draft;
}
const original = draft.__immer_original__;
const result = Array.isArray(original) ? [] : {};
let hasChanges = false;
// 收集所有键(包括原对象和新对象)
const allKeys = new Set([
...Object.keys(original || {}),
...Object.keys(draft)
]);
for (const key of allKeys) {
const originalValue = original ? original[key] : undefined;
const draftValue = draft[key];
// 如果属性被删除
if (!(key in draft)) {
hasChanges = true;
continue;
}
// 如果值是草稿,递归最终化
if (draftValue && draftValue.__immer_draft__) {
const finalized = this.finalize(draftValue);
if (finalized !== originalValue) {
hasChanges = true;
result[key] = finalized;
} else {
result[key] = originalValue;
}
}
// 如果值被修改
else if (draftValue !== originalValue) {
hasChanges = true;
result[key] = draftValue;
}
// 值未修改
else {
result[key] = originalValue;
}
}
return hasChanges ? result : original;
}
}
// 简化API
function produce(baseState, producer) {
const immer = new Immer(baseState);
return immer.produce(producer);
}
结语
Proxy和Reflect开启了JavaScript元编程的新篇章,它们不仅是框架开发的利器,也是理解现代JavaScript运行时特性的关键。对于文章中错误的地方或者有任何问题,欢迎在评论区留言讨论!