Vue3.0新特性之Proxy实现双向绑定

Vue3中的双向绑定Proxy

双向绑定这个概念应该已经不是个陌生的概念了,只要提到MVVM那就不得不提到它,我们来看Vue的三要素:

  • 响应式: Vue如何监听到data的每个属性变化
  • 模板引擎: Vue的模板如何被解析,指令如何处理
  • 渲染: Vue如何将监听到的数据变化和解析后的html进行渲染

其中第一要素响应式的实现方法,就是我们提到的双向绑定

在之前的文章中介绍了Vue2.0中基于数据劫持的双向绑定,使用的是Object.defineProperty()方法,但是这种方法在实现数据双向绑定时依然存在了许多不足和缺陷之处,因此,在即将到来的Vue3.0中,加入了Proxy来代替Object.defineProperty()方法,解决了2.0版本中关于双向绑定的若干缺陷。

Vue2.0中使用Object.definePorperty()的缺陷

在正式介绍Proxy之前,我们先来回顾一下使用Object.definePorperty()实现双向绑定时的不足和缺陷。

  1. Object.definePorperty()递归遍历所有对象的所有属性,当数据层级较深时,会造成性能影响。
  2. Object.definePorperty()只能作用在对象上,不能作用在数组上。
  3. Object.definePorperty()只能监听定义时的属性,不能监听新增属性。
  4. 由于Object.definePorperty()不能作用于数组,vue2.0选择通过重写数组方法原型的方式对数组数据进行监听,但是仍然无法监听数组索引的变化和长度的变更

vue2.0中基于数据劫持的双向绑定可以参考之前发布的这篇文章

Proxy

既然有问题了那就得解决,Vue3.0中就引入了Proxy

首先来关注一下Proxy到底是什么,以及它的具体用法:

Proxy是Es6中的新增特性,用于定义基本操作的自定义行为(如属性查找、赋值、枚举、函数调用等)

Proxy可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写,修改某些操作的默认行为,这样我们可以不直接操作对象本身,而是通过操作对象的代理对象来间接操作对象。Proxy这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。

ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例:

var proxy = new Proxy(target, handler)

target: 要拦截的对象

Proxy:也是一个对象,用于定制拦截行为

示例:

var proxy = new Proxy({}, {
  get(target, propKey) {
    return 35;
  }
});

proxy.time // 35
proxy.name // 35
proxy.title // 35

上述代码中,对proxy的所有属性读取操作都会被Proxy中的get()拦截,最终读取结果都是35

Proxy一共支持13种拦截操作:

拦截操作 描述
get(target, propKey, receiver) 拦截某个属性的读取操作
参数:目标对象、属性名和 proxy 实例本身(可选)
set(target, propKey, value, receiver) 拦截某个属性的赋值操作
参数:目标对象、属性名、属性值和 Proxy 实例本身(可选)
has(target, propKey) 拦截HasProperty操作,即判断对象是否具有某个属性,典型的操作就是in运算符。
参数:目标对象、需查询的属性名
apply() 拦截函数的调用、call和apply操作
参数:目标对象、目标对象的上下文对象(this)和目标对象的参数数组
construct(target, args, newTarget) 拦截new命令
目标对象、构造函数的参数对象、创造实例时,new命令作用的构造函数
deleteProperty (target, key) 拦截delete操作,如果这个方法抛出错误或者返回false,当前属性就无法被delete
defineProperty (target, key, descriptor) 拦截了Object.defineProperty()操作。
getOwnPropertyDescriptor (target, key) 拦截Object.getOwnPropertyDescriptor(),返回一个属性描述对象或者undefined。
getPrototypeOf(target) 拦截获取对象原型
isExtensible(target) 拦截Object.isExtensible()操作。
ownKeys(target) 拦截对象自身属性的读取操作
preventExtensions(target) 拦截Object.preventExtensions()。该方法必须返回一个布尔值,否则会被自动转为布尔值。
setPrototypeOf(target, proto) 拦截Object.setPrototypeOf()方法。

具体拦截操作用法和示例参照链接

本文关注的是Proxy在数据劫持中的表现,所以着重关注get(),set()的用法

在这之前,还得提到一下Reflect的用法:

Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与proxy handlers的方法相同。Reflect不是一个函数对象,因此它是不可构造的。

设计使用Reflect的目的主要有以下几点

  1. 将Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。
  2. 修改某些Object方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false。
  3. 让Object操作都变成函数行为。比如name in obj和delete obj[name],而Reflect.has(obj, name)和Reflect.deleteProperty(obj, name)让它们变成了函数行为。
  4. Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的Reflect方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy怎么修改默认行为,你总可以在Reflect上获取默认行为。

Proxy数据劫持原理

以下代码只是原理示例,非Vue源码

对于一个对象:

const data = {
    name: 'Proxy,
    info: {
        city:'Vue'
    },
    numbers: [1, 2, 3, 4]
}
function observe(data) {
  const that = this
  let handler = {
   get(target, property,receiver) {
      return Reflect.get(target, key, receiver)
    },
    set(target, key, value, receiver) {
      let res = Reflect.set(target, key, value, receiver)
      that.subscribe[key].map(item => {
        item.update()
      })
      return res
    }
  }
  this.$data = new Proxy(data, handler)
}

使用Proxy中的get()set()方法对所有的数据进行监听,只要我们Vue实列属性data中有任何数据发生改变的话,都会自动调用Proxy中的set方法,通过const rets = Reflect.set(target, key, value); return rets这样的代码,就是对`data中的任何数据发生改变后,使用该方法重新设置新值,然后返回给 this.$data保存到这个全局里面。

你可能感兴趣的:(Vue3.0新特性之Proxy实现双向绑定)