ES6中Reflect对象与Proxy结合实现代理和响应式编程

前言

在 JavaScript 中,Reflect 是一个内置对象,提供了拦截和操作 JavaScript 对象的元方法。它是 ES6 (ES2015) 引入的特性,主要用于简化元编程(meta-programming)并与 Proxy 结合使用实现对对象属性更细粒度的操作控制。

代理对象

Proxy 的第一个参数为要进行代理的目标对象,类型为Object,如果我们代理的目标是一个基础数据类型那应该怎么实现呢?

基础数据类型的代理

在 JavaScript 中,基础数据类型(如 numberstringboolean 等)无法直接被 Proxy 代理,因为 Proxy 只能拦截对象(包括数组、函数、类等)的操作。但可以通过对象封装的方式间接代理基础类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
const target  = {
value: '123' // 这里可以是number 、string、boolean...
}
const proxy = new Proxy(target, {
get (target, key, receiver){
// receiver 指向 proxy实例
// 思考 ? 此处可否直接返回 target[key]
return Reflect.get(target, key, receiver)
},
set (target, key, value, receiver){
return Reflect.set(target, key, value, receiver)
}
})

上述代码中有一个疑问,能否通过return target[key] 取代 return Reflect.get(target, key, receiver)? 事实上这里target[key] 等价于 Reflect.get(target, key), receiver是指向代理的实例,相当于call/apply的第一个参数,用于指定函数执行的上下文,上面例子中如果不涉及this指向,仅仅是对于简单类型的代理可使用Reflect.get(target, key) 或 target[key](但不推荐),接下来我们看下面这个例子说明不推荐的原因:

复杂数据类型的代理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
const people  = {
get name(){
console.log('thisArg:', this)
return this._name;
},
_name:'xixi'
}

const proxy1 = new Proxy(people, {
get (target, key, receiver){
// 思考 ? 此处可否直接返回 target[key]
console.log('get name:', target, receiver)
return Reflect.get(target, key, receiver);
},

})
console.log(proxy1.name)
// 输出:
// get name: { name: [Getter], _name: 'xixi' } name { name: [Getter], _name: 'xixi' }
// thisArg: { name: [Getter], _name: 'xixi' }
// get name: { name: [Getter], _name: 'xixi' } _name { name: [Getter], _name: 'xixi' }
// xixi

const proxy2 = new Proxy(people, {
get (target, key, receiver){
// 思考 ? 此处可否直接返回 target[key]
console.log('get name:', target, receiver)
return target[key];
},
})
console.log(proxy2.name)
// 输出:
// get name: { name: [Getter], _name: 'xixi' } name { name: [Getter], _name: 'xixi' }
// thisArg: { name: [Getter], _name: 'xixi' }
// xixi

结论:通过对比输出结果,可以看出直接return target[key]会导致this无法绑定在代理对象上,当修改属性时代理事件就无法触发导致错误,所以建议直接按照Reflect.get(target, key, receiver)处理。

响应式编程

此处我们以vue3的响应式编程为例,自定义实现ref和reactive的简化版:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87

// 存储依赖关系的 WeakMap
const targetMap = new WeakMap();

// 当前正在收集依赖的副作用函数
let activeEffect = null;

// 副作用函数
function effect(fn) {
activeEffect = fn;
fn(); // 执行副作用,触发依赖收集
activeEffect = null; // 清空
}

// 依赖收集函数
function track(target, key) {
if (activeEffect) {
let depsMap = targetMap.get(target);
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()));
}

let dep = depsMap.get(key);
if (!dep) {
depsMap.set(key, (dep = new Set()));
}

dep.add(activeEffect); // 将副作用添加到依赖集合
}
}

// 触发依赖更新
function trigger(target, key) {
const depsMap = targetMap.get(target);
if (!depsMap) return;

const dep = depsMap.get(key);
if (dep) {
dep.forEach(effect => effect()); // 执行所有依赖副作用
}
}

// 简化版 reactive
function reactive(target) {
return new Proxy(target, {
get(target, key, receiver) {
const result = Reflect.get(target, key, receiver);
track(target, key); // 收集依赖
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;
}
});
}

// 简化版 ref
function ref(initialValue) {
const _value = { value: initialValue }; // 创建一个包裹对象

// 对于对象类型,使用 reactive 转换为响应式
if (typeof initialValue === 'object' && initialValue !== null) {
_value.value = reactive(initialValue);
}

return new Proxy(_value, {
get(target, key) {
track(target, key); // 收集依赖
return target[key];
},
set(target, key, value) {
// 对于对象类型,使用 reactive 转换
if (key === 'value' && typeof value === 'object' && value !== null) {
target[key] = reactive(value);
} else {
target[key] = value;
}
trigger(target, key); // 触发更新
return true;
}
});
}
文章作者: Sir_Liu
文章链接: https://gofugui.github.io/2025/05/21/ES6中Reflect对象与Proxy结合实现代理和响应式编程/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Coding Your Life