IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    说说 Vue 响应式原理的细节!

    Grit的站点发表于 2023-11-08 03:01:16
    love 0
    该渲染由 Shiro API 生成,可能存在排版问题,最佳体验请前往:https://liu-wb.com/posts/technology/vue01

    在讲解之前,我们先了解一下数据响应式是什么?所谓数据响应式就是建立响应式数据与依赖(调用了响应式数据的操作)之间的关系,当响应式数据发生变化时,可以通知那些使用了这些响应式数据的依赖操作进行相关更新操作,可以是DOM更新,也可以是执行一些回调函数。从Vue2到Vue3都使用了响应式,那么它们之间有什么区别?

    • Vue2响应式:基于Object.defineProperty()实现的。
    • Vue3响应式:基于Proxy实现的。

    那么它们之间有什么区别?为什么Vue3会选择Proxy替代defineProperty?我们先看看下面两个例子:

    // 
    defineReactive(data,key,val){
        Object.defineProperty(data,key,{
          enumerable:true,
          configurable:true,
          get:function(){
            console.log(`对象属性:${key}访问defineReactive的get!`)
            return val;
          },
          set:function(newVal){
            if(val===newVal){
              return;
            }
            val = newVal;
            console.log(`对象属性:${key}访问defineReactive的set!`)
          }
        })
    }
    let obj = {};
    this.defineReactive(obj,'name','sapper');
    // 修改obj的name属性
    obj.name = '工兵';
    console.log('obj',obj.name);
    // 为obj添加age属性
    obj.age = 12;
    console.log('obj',obj);
    console.log('obj.age',obj.age);
    // 为obj添加数组属性
    obj.hobby = ['游戏', '原神'];
    obj.hobby[0] = '王者';
    console.log('obj.hobby',obj.hobby);
    
    // 为obj添加对象属性
    obj.student = {school:'大学'};
    obj.student.school = '学院';
    console.log('obj.student.school',obj.student.school);

    从上图可以看出使用defineProperty定义了包含name属性的对象obj,然后添加age属性、添加hobby属性(数组)、添加student属性并分别访问,都没有触发obj对象中的get、set方法。也就是说defineProperty定义对象不能监听添加额外属性或修改额外添加的属性的变化,我们再看看这样一个例子:

    let obj = {};
    // 初始化就添加hobby
    this.defineReactive(obj,'hobby',['游戏', '原神']);
    // 改变数组下标0的值
    obj.hobby[0] = '王者';
    console.log('obj.hobby',obj.hobby);

    假如我们一开始就为obj添加hobby属性,我们发现修改数组下标0的值,并没有触发obj里的set方法,也就是说defineProperty定义对象不能监听根据自身数组下标修改数组元素的变化,注意地,如果是直接用defineProperty定义数组元素是可以监听的,但是对于数组比较大的时候就很牺牲性能,尤神考虑到性能就没有使用这种方法。那么我们继续看一下Proxy代理的对象例子:

    // proxy实现
    let targetProxy = {name:'sapper'};
    let objProxy = new Proxy(targetProxy,{
        get(target,key){
          console.log(`对象属性:${key}访问Proxy的get!`)
          return target[key];
        },
        set(target,key,newVal){
          if(target[key]===newVal){
            return;
          }
          console.log(`对象属性:${key}访问Proxy的set!`)
          target[key]=newVal;
          return target[key];
        }
    })
    // 修改objProxy的name属性
    objProxy.name = '工兵';
    console.log('objProxy.name',objProxy.name);
    // 为objProxy添加age属性
    objProxy.age = 12;
    console.log('objProxy.age',objProxy.age);
    // 为objProxy添加hobby属性
    objProxy.hobby = ['游戏', '原神'];
    objProxy.hobby[0] = '王者';
    console.log('objProxy.hobby',objProxy.hobby);
    // 为objProxy添加对象属性
    objProxy.student = {school:'大学'};
    objProxy.student.school = '学院';
    console.log('objProxy.student.school',objProxy.student.school);

    从上图是不是发现了Proxy与defineProperty的明显区别之处了,Proxy能支持对象添加或修改触发get、set方法,不管对象内部有什么属性。所以

    • Object.defineProperty():defineProperty定义对象不能监听添加额外属性或修改额外添加的属性的变化;defineProperty定义对象不能监听根据自身数组下标修改数组元素的变化。

    我们看看Vue里的用法例子:

    data() {
       return {
         name: 'sapper',
         student: {
           name: 'sapper',
           hobby: ['原神', '天涯明月刀'],
         },
       };
     },
     methods: {
       deleteName() {
         delete this.student.name;
         console.log('删除了name', this.student);
       },
       addItem() {
         this.student.age = 21;
         console.log('添加了this.student的属性', this.student);
       },
       updateArr() {
         this.student.hobby[0] = '王者';
         console.log('更新了this.student的hobby', this.student);
       },
      }

    从图中确实可以修改data里的属性,但是不能及时渲染,所以Vue2提供了两个属性方法解决了这个问题:Vue.$set和Vue.$delete。

    注意不能直接this._ data.age这样去添加age属性,也是不支持的。

    this.$delete(this.student, 'name');// 删除student对象属性name
    this.$set(this.student, 'age', '21');// 添加student对象属性age
    this.$set(this.student.hobby, 0, '王者');// 更新student对象属性hobby数组

    • Proxy:解决了上面两个弊端,proxy可以实现:

    • 可以直接监听对象而非对象属性,可以监听对象添加额外属性的变化;

    const user = {name:'张三'}
    const obj = new Proxy(user,{
    get:function (target,key){
      console.log("get run");
      return target[key];
    },
    set:function (target,key,val){
      console.log("set run");
      target[key]=val;
      return true;
    }
    })
    obj.age = 22;
    console.log(obj); // 监听对象添加额外属性打印set run!
    • 可以直接监听数组的变化。
    const obj = new Proxy([2,1],{
    get:function (target,key){
      console.log("get run");
      return target[key];
    },
    set:function (target,key,val){
      console.log("set run");
      target[key]=val;
      return true;
    }
    })
    obj[0] = 3;
    console.log(obj); // 监听到了数组元素的变化打印set run!
    • Proxy 返回的是一个新对象,而 Object.defineProperty 只能遍历对象属性直接修改。

    • 支持多达13 种拦截方法,不限于 apply、ownKeys、deleteProperty、has 等等是Object.defineProperty 不具备的。

    总的来说,Vue3响应式使用Proxy解决了Vue2的响应式的诟病,从原理上说,它们所做的事情都是一样的,依赖收集和依赖更新。

    作者:前端有路灯 https://juejin.cn/post/7187285219257352250

    看完了?说点什么呢



沪ICP备19023445号-2号
友情链接