实现一个vue简易响应式

67 阅读2分钟

响应式数据的本质

vue实现响应式主要是通过 JavaScript 的 object.defineProperty  或者 vue3 的 proxy 来实现的

Object.defineProperty vs Proxy

  • 功能范围

    • definproperty

      • 只能对单个属性进行定义和控制
      • 需要为每个需要响应的属性单独定义 getter 和 setter 
      • 只能劫持对象现有的属性 无法对新添加的属性进行响应式处理
    • proxy

      • 可以拦截多种操作 包括 属性访问 赋值 删除 枚举 函数调用等
      • 可以对整个对象进行代理 所有属性都可以拦截
      • 可以动态地处理新添加的属性
  • 性能

    • definProperty

      • 需要为每个属性单独定义 getter 和 setter 可能会导致性能开销较大 尤其式在处理大量属性时
    • proxy

      • 由于可以一次性代理整个对象 性能上更优 尤其是需要动态添加属性的情况下
  • 兼容性

    • definproperty

      • 在es5中引入 兼容性较好 支持较早的浏览器
    • proxy

      • 在es6中引入兼容性较差 某些旧版浏览器不支持

Proxy实现

// 当前活跃的依赖
let activeRelyOn = null;
// 依赖管理器
class Dep {
  // 存储依赖的 watcher 
  constructor() {
    this.subrelyon = new Set();
  }
  // 添加依赖
  depAddRelyOn() {
    if (activeRelyOn){
      this.subrelyon.add(activeRelyOn);
    }
  }
  // 通知依赖更新
  depRelyOnUpdate(){
    this.subrelyon.forEach(affect=>affect())
  }
}

// 响应函数
let responseReactive = ()=>{
  // 创建一个依赖管理器
  const dep = new Dep();
  return new Proxy(target,{
    get(target,key){
      // 收集依赖
      dep.depAddRelyOn();
      // 返回属性值
      return Reflect.get(target,key);
    },
    set(target,key,value){
      // 
      console.log(target);
      const relyOnKey = target[key];
      // 设置属性值
      const result = Reflect.set(target.key,value);
      if(relyOnKey !== value){
        dep.depRelyOnUpdate();
      }
      // 返回设置结果
      return result;
    }
  })
}


let ecflect = (fn)=>{
  // 设置当前活跃的 wetcher 
  activeRelyOn = fn;
  fn();
  // 清空当前活跃的wetcher
  activeRelyOn = null; 
}

// 创建一个响应对象 
let relyOnObj = {
  name:"测试对象",
}

// 使用ecflect监听当前活跃watcher的变化
ecflect(()=>{
  console.log(`Name: ${relyOnObj.name}`);
})

// 修改响应式对象的属性
relyOnObj.name = 'Vue 3';