前言 在 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 ; } }); }