vue3响应式核心proxy带来质的提升,附带对比vue2响应式原理

1,000 阅读4分钟

背景:

之前写了通过理解 Object.defineProperty用法去理解 vue2 响应式原理的文章。所以就最近抽空写一篇通过理解Proxy代理去理解 vue3响应式 的文章,而且通过学会底层实现方式去对比 vue2vue3 响应式变化肯定理解上会更直观。下面将从基本介绍和用法去写。

附带vue2响应式原理理解地址:通过学会Object.defineProperty的用法理解vue2响应式原理地址

一、Proxy的作用简述:

ProxyES6 中新增的一个函数,也可以叫类, Proxy 本质就是一种构造器,所以我们要通过 new 去实现的。Proxy 的作用是一种拦截器,即在设置对象属性或者读取对象属性时做一层拦截操作。

先说一个注意事项:Proxy没有原型对象
由于Proxy构造出来的实例对象是对目标对象的一个代理,因此 Proxy 在构造过程中是不需要 prototype 进行初始化的,因为其他构造函数之所以需要 prototype,是因为构造出来的对象需要一些初始化的成员,所以将这些成员定义到了 protoype 上,Proxy 只是用来代理其他对象,所以本身不需要prototype

二、Proxy具体用法:

基本语法是:new Proxy(target, handler);

其中:

  • 第一个参数target为被代理对象
  • 第二个参数handler为做拦截需要做什么操作即set、get等方法。

其中第二个参数包含众多方法,例如:

        handler.getPrototypeOf()  // 在读取代理对象的原型时触发该操作
        
        handler.setPrototypeOf()  // 在设置代理对象的原型时触发该操作
    
        handler.isExtensible()  // 在判断一个代理对象是否是可扩展时触发该操作
  
        handler.preventExtensions()  // 在让一个代理对象不可扩展时触发该操作
    
        handler.getOwnPropertyDescriptor()  // 在获取代理对象某个属性的属性描述时触发该操作
       
        andler.defineProperty()   // 在定义代理对象某个属性时的属性描述时触发该操作
       
        handler.has()   // 在判断代理对象是否拥有某个属性时触发该操作
        
        handler.get()  // 在读取代理对象的某个属性时触发该操作
        
        handler.set()   // 在给代理对象的某个属性赋值时触发该操作
        
        handler.deleteProperty()  // 在删除代理对象的某个属性时触发该操作
        
        handler.ownKeys()  // 在获取代理对象的所有属性键时触发该操作
       
        handler.apply()   // 在调用一个目标对象为函数的代理对象时触发该操作
        
        handler.construct()  // 在给一个目标对象为构造函数的代理对象构造实例时触发该操作

重点来了,我们理解vue3响应式原理思路主要通过get和set就行:

get方法有三个参数get(target, key, proxy)分别为目标对象、键、实例对象本身

set方法有四个参数 set(target, key, value, proxy),分别为目标对象、键、值、实例对象本身。

(1)看看get如何拦截属性访问

const target = { name: '天天鸭' };
const handler = {
  get: function (target, prop) {
    console.log("get天天鸭");
    if (prop in target) {
      return target[prop];
    } else {
      return 'Property does not exist';
    }
  }
};

const test = new Proxy(target, handler);

test.name     // 当获取任何对象属性内容时, 打印 "get天天鸭"

主要留意最后一行代码, 当获取任何对象属性内容时,都会触发get方法.

(2)看看set如何拦截属性修改

const target = { name: '天天鸭' };
const handler = {
  set: function (target, prop, value) {
    console.log('触发了set');
    target[prop] = value;
    return true;
  }
};

const test = new Proxy(target, handler);

test.name = '天天鸭2'; // 只要做任何修改属性的行为会打印 '触发了set'

主要留意最后一行代码, 做任何修改属性的行为都会 触发了set方法

小结一下:

结合如上代码例子所示,通过认识get、set方法就知道proxy 是对整个对象进行了拦截,只要获取里面任何一个属性内容就会触发 get 方法,设置对象任何一个属性就触发 set 方法。这样就能通过它知道什么时候应该更新视图,结合依赖收集机制实现了vue3的响应式。

最终对比 vue2Object.defineProperty 是通过递归去拦截对象内的所有属性,而Proxy 是对整个完整对象进行拦截的,因此解决了 vue2 对数组下标无法监听的问题,这也正是 vue3 使用 Proxy 而放弃了 Object.defineProperty 的根本原因。