前言
在 JavaScript 中,Reflect 是一个内置对象,提供了拦截和操作 JavaScript 对象的元方法。它是 ES6 (ES2015) 引入的特性,主要用于简化元编程(meta-programming)并与 Proxy 结合使用实现对对象属性更细粒度的操作控制。
代理对象
Proxy 的第一个参数为要进行代理的目标对象,类型为Object,如果我们代理的目标是一个基础数据类型那应该怎么实现呢?
基础数据类型的代理
在 JavaScript 中,基础数据类型(如 number、string、boolean 等)无法直接被 Proxy 代理,因为 Proxy 只能拦截对象(包括数组、函数、类等)的操作。但可以通过对象封装的方式间接代理基础类型:
1 2 3 4 5 6 7 8 9 10 11 12 13
| const target = { value: '123' } const proxy = new Proxy(target, { get (target, key, receiver){ 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){ console.log('get name:', target, receiver) return Reflect.get(target, key, receiver); }, }) console.log(proxy1.name) const proxy2 = new Proxy(people, { get (target, key, receiver){ console.log('get name:', target, receiver) return target[key]; }, }) console.log(proxy2.name)
|
结论:通过对比输出结果,可以看出直接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
|
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()); } }
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; } }); }
function ref(initialValue) { const _value = { value: initialValue }; 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) { if (key === 'value' && typeof value === 'object' && value !== null) { target[key] = reactive(value); } else { target[key] = value; } trigger(target, key); return true; } }); }
|