【源码解读】VUE2.0和VUE3.0响应式区别?

数据劫持的目的

VUE2.0和VUE3.0实现响应式的底层逻辑,是对数据做劫持,为什么要劫持数据呢?是因为,劫持数据后才可以,在更改数据同时对页面进行重新渲染,从而达到响应式。

VUE3.0响应原理

VUE3.0使用了ES6 proxy做代理,proxy是用于创建一个对象的代理,可实现基本操作的拦截和自定义。简而言之,proxy可以在数据做操作时,进行拦截,其基础语法是:const p = new Proxy(target,handler) target为要操作的obj,handler是数据的set,get操作方法。
举个例子:

   let test = reactive({ a: 1, b: 2 });
    function reactive(target) {
      const testProxy = new Proxy(target, {
        get(target, key, receiver) {
          //拦截后可以做很多事情,比如视图渲染
          const res = Reflect.get(target, key, receiver); //等同于res=target[key]
          console.log("响应式获取:" + res);
          return res;
        },
        set(target, key, value, receiver) {
        //拦截后可以做很多事情,比如视图渲染
          const res = Reflect.set(target, key, value, receiver); //等同于设置target[key]=value
          console.log("响应式设置:" + res);
        }
      });
      return testProxy;
    }

    test.a;
    test.a = 3;

运行结果:运行结果
以上例子是reactive核心思想,其本质也就是对对象进行了代理,并返回代理对象。在get、set方法中使用了Reflect对象,**注意:这是个对象,并不是方法。**官方解释,Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。那为什么在这里使用Reflect对象呢?因为,Reflect与Proxy的get(),set()方法一一对应,一个Proxy的get方法对应一个Reflect.get()方法,如果使用obj.defineProperty方法修改两次同一属性的,就会报错。JS又是单线程的,报错后不会继续执行下面代码,则框架的健壮性就会很差。

    let test = { a: 1, b: 2 };
    Object.defineProperty(test, "c", {
      get() {
        return 7;
      }
    });
    Object.defineProperty(test, "c", {
      get() {
        return 99;
      }
    });

在这里插入图片描述
但如果使用Reflect,就不会报错,会将数据反射出去。

    let test = { a: 1, b: 2 };

    const t1 = new Proxy(test, {
      get() {
        return 7;
      }
    });
    console.log("t1.c=" + t1.c);
    const t2 = new Proxy(test, {
      get() {
        return 99;
      }
    });
    console.log("t2.c=" + t2.c);

在这里插入图片描述

VUE2.0响应式原理

vue2.0响应式是通过Object.defineProperty方法,对属性进行劫持。官方文档解释,Object.defineProperty() 静态方法会直接在一个对象上定义一个新属性,或修改其现有属性,并返回此对象。基础语法是:

Object.defineProperty(obj, prop, descriptor)

obj
要定义属性的对象。

prop
一个字符串或 Symbol,指定了要定义或修改的属性键。

descriptor
要定义或修改的属性的描述符。

可以看出,Object.defineProperty是需要对对象里的每个属性,一一进行设置或者监听。在VUE2中,也是使用该核心思想,但此方法只可以对对象进行监听,数组Array监听则采用对数组原型方法重写,进行数据拦截。

  • 对象Object监听
let test = { a: 1, b: 2 };
    observer(test);
    function observer(obj) {
      let testKeys = Object.keys(test);
      testKeys.map((key) => {
        defineProperty(obj, key, obj[key]);
      });
    }
    function defineProperty(obj, key, val) {
      Object.defineProperty(obj, key, {
        get() {
          console.log("响应式获取:" + val);
          return val;
        },
        set(newval) {
          console.log("响应式设置:" + key + "=" + newval);
          val = newval;
        }
      });
    }
    console.log(test.a);
    //响应式获取:1
    // 1
    test.a = 5;
    //响应式设置:a=5
    console.log(test.a);
     //响应式获取:5
    // 5

源码核心思想如上,会建立一个observer监听对象,并在defineProperty中实现对每个属性的数据拦截。
如果是多层对象嵌套,可通过递归方式,对属性中数据做深入拦截。

let test = { a: 1, b: { c: 2, d: 3 } };
    observer(test);
    function observer(obj) {
      if (typeof obj !== "object") return;
      let testKeys = Object.keys(obj);
      testKeys.map((key) => {
        defineProperty(obj, key, obj[key]);
      });
    }
    function defineProperty(obj, key, val) {
      if (typeof val === "object" || val !== null) {
        observer(val);
      }
      Object.defineProperty(obj, key, {
        get() {
          console.log("响应式获取:" + val);
          return val;
        },
        set(newval) {
          console.log("响应式设置:" + key + "=" + newval);
          val = newval;
        }
      });
    }
  • 数组Array监听
    数组的数据劫持,主要是对数组中,会影响数组结构和内容的方法,在原型链上重写。这些方法包括push、pop、shifit等,这是因为数组结构发生变化,需要视图进行同步更新,所以需要对这些方法进行劫持。
 const customMethods = [
      "push",
      "pop",
      "shifit",
      "unshift",
      "splice",
      "sort",
      "reverse"
    ];
    const originArrMethods = Array.prototype, //获取Array上的原型对象,包含所有方法
      arrMethods = Object.create(originArrMethods); //创建一个和原型方法一样的对象
    customMethods.map((m) => {
      //重写方法
      arrMethods[m] = function () {
        const args = Array.prototype.slice.call(arguments), //将入参处理为数组
          rt = originArrMethods[m].apply(this, args); //将this指向改为原始Array
        let newArr;
        switch (m) {
          case "push":
          case "unshift":
            newArr = args;
            break;
          case "splice": //splice(start,end,新值)
            newArr = args.slice(2); //取新值
            break;
          default:
            break;
        }
        return rt;
      };
    });
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值