美文网首页
简述 Vue响应式数据的原理

简述 Vue响应式数据的原理

作者: 简单tao的简单 | 来源:发表于2023-11-30 17:31 被阅读0次

    vue2

    vue2 利用 es5 的 Object.defineProperty 实现响应式

    下面详解一下 Object.defineProperty

    • 定义
      Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象
    • 语法:
      Object.defineProperty(obj, key, descriptor)
    • 参数说明:
      1、obj要在其上定义属性的对象
      2、要定义或修改的属性的名称
      3、将被定义或修改的属性描述符
    • 返回值:
      被传递给函数的对象

    下面我们通过使用 Object.defineProperty 创建一个对象来熟悉它的用法

    正常创建对象这样就可以了

    let vm = {
       name: '简书'
    }
    

    使用 Object.defineProperty 创建对象

    let vm = Object.defineProperty({},"name",{
      get() {
        console.log("执行get");
        return name || "简书"
      },
      set(newVal) {
        console.log("执行set");
        console.log("新值:" + newVal);
        if (newVal !== name) {
          name = newVal;
        }
      }
    })
    

    其实这两种都创建一个对象的方式,通过Object.defineProperty创建的对象,我们可以看到,多了上面说的两个存取描述符键值方法get 和set 这样的对象,就变得可控被观察,也就是我们说的被劫持,当我们改变或者获取这对象的属性的时候,我们就可以控制到它

    下面我们改变vm.name = "1",我们通过控制台可以看到

    实现观察者机制,响应式对象

    let vm = {
      id:"jianshu",
      name:"简书"
    };
    let keys = Object.keys(vm); //Object.keys() 方法会返回一个数组,数组元素就是vm对象的属性名 ['id', 'name']
    keys.forEach(key=>{ //keys.forEach 没有返回结果,返回值为undefined,本质上等同于 for 循环,会改变原数组
      let value = vm[key];
      Object.defineProperty(vm, key,{
        get() {
          console.log("执行get");
          return value
       },
       set(newVal){
          console.log("执行set");
          if(newVal !== value){
             value =  newVal;
           }
         }
      })
    })
    vm.id = "test";
    console.log(vm)
    

    控制台输出


    这里主要是遍历对象中的每一个属性,每个属性都是赋予get和set,让对象中的每一个属性的改变都会被监测到,检测到改变后,vue会触发rerender函数,重新渲染页面,也就是实现了响应式

    Object.defineProperty 的缺点
    1. 无法监听对象非已有的属性的添加和已有属性的删除
      只会对对象原有的全部属性进行做数据劫持,也就是说Vue 不允许动态添加或者删除对象已有属性,它是不做数据劫持的,也就不能实现响应式
      举例
    <template>
    <div>
    
    <h1>{{ vm }}</h1>
    <button @click="addAttribute">新增属性</button>
    <button @click="delAttribute">删除属性</button>
    </div>
    </template>
    
    <script>
    export default {
    data() {
    
    return { 
      vm:{
        id:"juejin",
        name:"掘金"
      }
    }
    },
    methods: {
    
    addAttribute() {
      this.vm.use = "codercao"
      console.log(this.vm)
    },
    delAttribute() {
      for(let k in this.vm) {
       if(k=='id'){
         delete this.vm[k]
       }
      }
      console.log(this.vm)
    }
    },
    }
    </script>
    

    新增,你会发现控制台打印的vm已经新增了use属性,而页面并没有响应式改变
    删除,你会发现控制台打印的vm已经删除了id属性,而页面并没有响应式改变

    1. 数组对象也不能通过属性或者索引(length,index)实现响应式
      vue源码只是对数组的 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'进行了重写,但是索引控制数组是没有办法实现响应式的

    解决问题

    Object.defineProperty 的缺点1可以使用Vue.set(object, propertyName, value) 方法解决,你会发现页面就实现了响应式

    addAttribute() {
     //this.vm.use = "codercao"
     this.$set(this.vm,'use','codercao')
     console.log(this.vm)
    },
    

    vue3

    vue3 利用 ES6 的 proxy 实现响应式

    下面详解一下 Object.defineProperty

    • 定义
      Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)
    • 通俗解释
      假如我们有一个对象 obj,使用 Proxy 对象可以给我们创建一个代理,就好比我们打官司之前,可以先去律师事务所找一个律师,律师全权代理我们。有了这个代理之后,就可以对这个 obj 做一些拦截或自定义,比如对方想要直接找我们谈话时,我们的律师可以先进行拦截,他来判断是否允许和我谈话,然后再做决定。这个律师就是我们对象的代理,有人想要修改 obj 对象,必须先经过律师那一关。
    • 语法
      let p = new Proxy(target, handler);
    • 参数
      target 是需要被代理的对象,它可以是任何类型的对象,比如数组、函数等等,注意不能是基础数据类型。
      我们使用 new 关键词后生成了一个代理对象 p,它就和我们原来的对象 obj 一样,只不过它是一个 Proxy 对象,我们打印出来看看就能更好理解了。
    <script>
      let obj = {
        name: '小猪课堂',
        age: 23
      }
      let p = new Proxy(obj, {});
      console.log(obj);
      console.log(p);
    </script>
    

    上面只是简单的初始化了一个代理对象,而我们需要重点掌握的是 Proxy 对象中的 handler 参数。因为我们所有的拦截操作都是通过这个对象里面的函数而完成的。
    就好比律师全权代理了我们,那他拦截之后能做什么呢?或者说律师拦截之后他有哪些能力呢?这就是我们 handler 参数对象的作用了,接下来我们就一一来讲解下。

    handler 它是一个对象,该对象的属性通常都是一些函数,handler 对象中的这些函数也就是我们的处理器函数,主要定义我们在代理对象后的拦截或者自定义的行为。

    //handler 对象的的属性大概有下面这些
    handler.apply()
    handler.construct()
    handler.defineProperty()
    handler.deleteProperty()
    handler.get()
    handler.getOwnPropertyDescriptor()
    handler.getPrototypeOf()
    handler.has()
    handler.isExtensible()
    handler.ownKeys()
    handler.preventExtensions()
    handler.set()
    handler.setPrototypeOf()
    

    这里主要讲解一下 handler.get() 和 handler.set(),其他的 handler属性不在此过多阐述,因为我们是为了讲解Vue响应式数据的原理

    handler.get

    该方法用于拦截对象的读取属性操作,比如我们要读取某个对象的属性,就可以使用该方法进行拦截。

    • 语法
    // 拦截读取属性操作
    let p5 = new Proxy(target, {
      get: function (target, property, receiver) {
            //target:被代理的目标对象
            //property:想要获取的属性名
            //receiver:Proxy 或者继承 Proxy 的对象
      }
    });
    
    • 案例
    // 拦截读取属性操作
    let p5 = new Proxy({}, {
        get: function (target, property, receiver) {
            console.log("属性名:", property); // 属性名:name
            console.log(receiver); // Proxy {}
            return target[property] || '小猪课堂'
        }
    });
    console.log(p5.name); // 小猪课堂
    

    可以看到我们代理的对象其实是一个空对象,但是我们获取 name 属性是是返回了值的,其实是在 handler 对象中的 get 函数返回的。

    handler.set

    当我们给对象设置属性值时,将会触发该拦截

    • 语法
    let p12 = new Proxy(target, {
      set: function (target, property, value, receiver) {
            //target:被代理的目标对象
            //property:将要被设置的属性名
            //value:新的属性值
            //receiver:最初被调用的对象,通常就是 proxy 对象本身
      }
    });
    
    • 以下操作会触发拦截:
      1.指定属性值:proxy[foo] = bar 和 proxy.foo = bar
      2.指定继承者的属性值:Object.create(proxy)[foo] = bar
      3.Reflect.set()
    • 案例
    let p12 = new Proxy({}, {
      set: function (target, property, value, receiver) {
        target[property] = value;
        console.log('property set: ' + property + ' = ' + value); // property set: a = 10
        return true;
      }
    });
    p12.a = 10; 
    
    • 注意
      set() 方法应当返回一个布尔值

    好了,学习了proxy的原理,我们再看下如下代码的控制台输出

    let vm = {
      id:"jianshu",
      name:"简书"
    }
    let newVm = new Proxy(vm,{
      get(target,key){
        console.log("执行get");
        return target[key];
       },
      set(target,key,newVal){
        console.log("执行set");
        if(target[key]!==newVal)
        target[key] = newVal;
      }
    })
    newVm.use = "codercao" //会触发set拦截
    console.log(newVm)
    

    你会发现新增的属性也是响应式的,用Proxy 也一样实现了一个简易的观察者机制,当然深入研究的话,你还可以实现双向绑定。

    总结

    • Object.defineProperty 是监听data里现有的属性,所以data对象有新增的属性是监听不到的
    • proxy是监听data这个对象,所以data对象上有新增的属性也会被监听到

    相关文章

      网友评论

          本文标题:简述 Vue响应式数据的原理

          本文链接:https://www.haomeiwen.com/subject/hgsfgdtx.html