1. Proxy 代理
定义: 用于定义基本操作的自定义行为
Proxy修改的是程序默认形为,就形同于在编程语言层面上做修改,属于元编程
元编程 是指某类计算机程序的编写,这类计算机程序编写或者操纵其它程序(或者自身)作为它们的数据,或者在运行时完成部分本应在编译时完成的工作
元编程优点 与手工编写全部代码相比,程序员可以获得更高的工作效率,或者给与程序更大的灵活度去处理新的情形而无需重新编译
大概意思就是 我给你封装了一层,在我操作你的中间加了一段路径,可以用来处理,监听,截停等操作,简称拦路虎
Proxy 译为代理,可以理解为在操作目标对象前架设一层代理,将所有本该我们手动编写的程序交由代理来处理,生活中也有许许多多的“proxy”, 如代购,中介,因为他们所有的行为都不会直接触达到目标对象
2. Proxy的使用
let p = new Proxy(target, handler);
target:用Proxy包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
handler:一个对象,其属性是当执行一个操作时定义代理的行为的函数。
let yu = { age: 19, height: 155 }
let p = new Proxy(yu, {get: (target, property) => {if (property === 'age') {return target.age + 6} else if (property === 'height') {return target.height * 2}}
})p.age // 25
yu.age // 19p.height // 310
yu.height // 155
2.1 Handler 对象常用的方法
handler.get上面已经用过了,它其实接受三个参数 get(target, propKey, ?receiver)
- target 目标对象
- propkey 属性名
- receiver Proxy 实例本身
其他的都大同小异,差不多
2.2 可撤消的Proxy
proxy有一个唯一的静态方法 ------- proxy.revocable(target, handler)
这个方法可以用来创建一个可撤销的代理对象
该方法的返回值是一个对象,其结构为: { “proxy”: proxy,“revoke”: revoke }
- proxy 表示新生成的代理对象本身,和用一般方式 new Proxy(target, handler) 创建的代理对象没什么不同,只是它可以被撤销掉
- revoke 撤销方法,调用的时候不需要加任何参数,就可以撤销掉和它一起生成的那个代理对象
const target = { name: 'yu'}
const {proxy, revoke} = Proxy.revocable(target, handler)
proxy.name // 正常取值输出 vuejs
revoke() // 取值完成对proxy进行封闭,撤消代理
proxy.name // TypeError: Revoked
3. proxy和Object.defineProperty
在proxy之前,vue2用的是Object.defineProperty,允许对对象的setter/getter进行拦截,Vue3.0之前的双向绑定是由defineProperty实现的,在3.0重构为 Proxy,那么两者的区别究竟在哪里呢?
定义: Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象
上面给两个词划了重点,对象上,属性,我们可以理解为是针对对象上的某一个属性做处理的
语法:
Object.defineProperty(obj, prop, descriptor)
- obj 要定义属性的对象
- prop 要定义或修改的属性的名称或 Symbol- descriptor 要定义或修改的属性描述符
const obj = {}
Object.defineProperty(obj, "a", {value : 1,writable : false, // 是否可写 configurable : false, // 是否可配置enumerable : false // 是否可枚举
})// 上面给了三个false, 下面的相关操作就很容易理解了
obj.a = 2 // 无效
delete obj.a // 无效
for(key in obj){console.log(key) // 无效
}
3.1 Vue2中的defineProperty
Vue2的双向绑定都是通过 defineProperty
的 getter,setter
来实现的
const obj = {};
Object.defineProperty(obj, 'a', {set(val) {console.log(`开始设置新值: ${val}`)},get() { console.log(`开始读取属性`)return 1; },writable : true
})obj.a = 2 // 开始设置新值: 2
obj.a // 开始获取属性
3.2 defineProperty的缺点
Vue在初始化时会对data对象的属性进行数据劫持,但是对于后续新增的属性,Vue无法自动进行响应式处理。Vue 无法探测普通的新增属性
对象
这也就是为什么对象的新增属性为什么不更新
data () {return {obj: {a: 1}}
}methods: {update () {this.obj.b = 2}
}
这个其实很好理解,我们先要明白 vue 中 data init
的时机,data init
是在生命周期 created
之前的操作,会对 data
绑定一个观察者 Observer
,之后 data
中的字段更新都会通知依赖收集器Dep
触发视图更新
然后我们回到 defineProperty
本身,是对对象上的属性做操作,而非对象本身
一句话来说就是,在 Observer data
时,新增属性并不存在,自然就不会有 getter, setter
,也就解释了为什么新增视图不更新,解决有很多种,Vue
提供的全局$set
本质也是给新增的属性手动 observer
利用delete删除对象的属性,无法被Vue监测到
数组
还有一个就是数组了,由于 JavaScript 的限制,Vue 不能检测以下数组的变动:数组索引设置或者长度改变不是响应式的
var vm = new Vue({data: {items: ['1', '2', '3']}
})
vm.items[1] = '4' // 视图并未更新
解决方法:使用数组的 push()
、pop()
、shift()
、unshift()
、splice()
、sort(),
reverse()
方法来确保数组的变化是响应式的
3.3 总的来说
-
Object.definedProperty
是劫持对象的属性,新增元素需要再次definedProperty
。而Proxy
劫持的是整个对象,不需要做特殊处理 -
使用
defineProperty
时,我们修改原来的obj
对象就可以触发拦截,而使用proxy
,就必须修改代理对象,即Proxy
的实例才可以触发拦截