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

12 阅读3分钟

前言

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

代理对象

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

基础数据类型的代理

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

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](但不推荐),接下来我们看下面这个例子说明不推荐的原因:

复杂数据类型的代理

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的简化版:


// 存储依赖关系的 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;
    }
  });
}