双向绑定这个概念应该已经不是个陌生的概念了,只要提到MVVM那就不得不提到它,我们来看Vue的三要素:
其中第一要素响应式的实现方法,就是我们提到的双向绑定
在之前的文章中介绍了Vue2.0中基于数据劫持的双向绑定,使用的是Object.defineProperty()
方法,但是这种方法在实现数据双向绑定时依然存在了许多不足和缺陷之处,因此,在即将到来的Vue3.0中,加入了Proxy
来代替Object.defineProperty()
方法,解决了2.0版本中关于双向绑定的若干缺陷。
Object.definePorperty()
的缺陷在正式介绍Proxy
之前,我们先来回顾一下使用Object.definePorperty()
实现双向绑定时的不足和缺陷。
Object.definePorperty()
递归遍历所有对象的所有属性,当数据层级较深时,会造成性能影响。Object.definePorperty()
只能作用在对象上,不能作用在数组上。Object.definePorperty()
只能监听定义时的属性,不能监听新增属性。Object.definePorperty()
不能作用于数组,vue2.0选择通过重写数组方法原型的方式对数组数据进行监听,但是仍然无法监听数组索引的变化和长度的变更vue2.0中基于数据劫持的双向绑定可以参考之前发布的这篇文章
既然有问题了那就得解决,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
的目的主要有以下几点
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保存到这个全局里面。