开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 4 天,点击查看活动详情
说点题外话
上一篇说了vue2使用Object.defineProperty作为底层原理的缺点,这一篇就看看vue3如何解决这个缺点的
说正文
先回顾一个Vue2的缺点:数组或者对象新增的属性和索引都无法监听,需要手动重新触发侦听
vue3的Proxy
和vue2的Object.defineProperty
- vue2是给每个对象添加一个getter和setter属性去改变对象,实现对数据的检测。
- Object.defineProperty和Proxy的本质区别是:defineProperty只对属性进行劫持,所以出现了需要递归遍历,新增属性需要手动重新侦听的问题。
Proxy
代理的是整个对象(侦听的是整个对象或数组而不是属性), 并且一开始只代理最外层的对象,性能更好。Proxy内置了13种拦截方法,是definePorperty没有的优势。
那你肯定还会有很多疑问?
1. Proxy如果只侦听最外层,那么Vue3怎么实现震度侦听呢?
判断当前Reflect.get的返回值是否还是个对象,如果是对象那么就继续执行代理,用来实现深度侦听。
Proxy详解
我们知道了proxy的优势,那么它是如何使用呢
const p = new Proxy(target, handler)
-
target
: 要使用Proxy
包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。 -
handler
: 一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理p
的行为。
我举个例子验证一下proxy是如何实现侦听的
const person = { name: "小白" }
const proxy = new Proxy(person, {
get(target, key, receiver) {
console.log("target: ", target, "key:", key, "receiver:", receiver)
},
set(target, key, value, receiver) {
console.log("target: ", target, "key:", key, "value:",value, "receiver:", receiver)
}
})
proxy.name
此时的输出结果是target: {name: '小白'} key: name receiver: Proxy {name: '小白'}
, 触发了get方法。
proxy.name = "小刚"
当我们改变name的属性值后,输出结果是:target: {name: '小白'} key: name value: 小刚 receiver: Proxy {name: '小白'}
这里就可以看出来proxy的强大了,毕竟这是Object.defineProperty没做到的事情。
proxy的强大不止于此,它一般和Reflect搭配使用。
const person = { name: "小白" }
function observe(target) {
return new Proxy(target, {
get(target, key, receiver) {
console.log(`访问了${key}属性`)
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {
console.log(`${key}由->${target[key]}->设置成->${value}`)
Reflect.set(target, key, value, receiver)
}
})
}
const proxy = observe(person)
console.log(proxy.name)
输出结果是
这里和不使用reflect一样直接获取属性的值会触发get方法,但是reflect会返回一个value值
proxy.name = '小刚'
console.log(proxy.name)
这里的输出结果是
不仅触发了get方法也触发了set方法进行更新。以上proxy体现出来的和defineProperty差不多,那么如果是新增的属性呢
proxy.age = 18
console.log(proxy.age)
当新增一个对象属性之后,会同时触发get和set方法,这一点与defineProperty就不同了。
3. Reflect
在说说为什么Proxy搭配Reflect使用,先看看Reflect方法是怎么用的,这里就说说上面使用的get方法和set方法
-
Reflect.get(target, propertyKey[, receiver])
: 获取对象身上某个属性的值,类似于target[name]
。 -
Reflect.set(target, propertyKey, value[, receiver])
: 将值分配给属性的函数。返回一个Boolean
,如果更新成功,则返回true
。
我们也可以理解为reflect.get(target, key, receiver) 中的receiver在target指定getter的情况下是调用getter的this值。当我们访问target的key值时,this是指向receiver的,所以实际上我们访问的是receiver的key值,但是这可不是直接访问receiver[key]
const person = { name: '小白' }
const proxy = new Proxy(person, {
get(target, key, receiver) {
return Reflect.get(receiver, key)
},
set(target, key, value, receiver) {
Reflect.set(receiver, key, value)
}
})
console.log(person.name)
proxy.name = '小刚'
这段代码输出结果是
因为 Reflect.get(receiver, key)
相当于receiver[key], 又会触发get方法直接陷入死循环。
说再见
下一篇就详细说说MVVM响应式原理,面试题直接开业大酬宾
难忘今宵
为什么有些人爱抖腿?
抖腿这种行为可分为两种情况:一是日常抖腿,属于人在生活中不自觉养成的习惯;二是属于疾病性抖腿,就算腿的主人想停止,但却无法控制自己的抖腿行为。