Veu3 采用proxy来实现数据监听 放弃了Object.defineProperty
我们都知道在Vue2 中采用了数据劫持结合发布者,订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的getter,setter,在数据变动的时候发布消息给订阅者,触发相应的监听回调。这个仍发是有缺点的,并不能实现数组和对象的部分监听,所以在最新的Vue3采用了Proxy,相比vue2的Object.defineProperty()能达到速度加倍、内存减半的效果。
首先看一下Vue init 过程
Vue初始化的过程分别是Observer、Compiler和Watcher
当我们new Vue的时候,会调用Observer,通过Object.defineProperty便利vue对象的实力(data,computed,props(如果是组件的话))的所有属性进行监听,同事通过Compiler解析模板指令,解析到属性后就new 一个watcher并绑定更新函数到watcher当中,Compiler和Watcher就通过属性进行关联。
当 Observer 中的 setter 检测到属性值改变的时候,就调用属性对应的所有 watcher 调用更新函数,从而更新到属性对应的 dom
来个简单的Object.defineProperty()例子
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- 表单 -->
<input type="text" id="input">
<!-- 展示 -->
<p id="desc"></p>
<script>
let obj = {};
let temp = {};//采用临时变量代理obj
Object.defineProperty(obj,'name',{
//获取obj的name属性会触发
get(){
return temp['name'];
},
//给obj的name属性赋值会触发
set(val){
temp['name'] = val;//改变temp的结果
input.value = val;//将值赋值到输入框
desc.innerText = val; //将值显示到输入框下面
//obj.name = val; //死循环,不能采取这种方式赋值,采用temp代理方式赋值和取值
}
});
//设置了id值不需要document.getElementById()
//调用上面的set方法,设置初始值
obj.name = "";
//调用上面的get方法,获取属性值并放到输入框
input.value = obj.name;
//输入框的变化时执行,这里不能使用箭头函数,因为箭头函数不绑定this,找的是上下文的this
input.addEventListener('input',function(){
//当值变化时会调用set方法
obj.name = this.value;
});
</script>
</body>
</html>
效果如下:
从上面的例子可以知道:
1、Object.defineProperty 需要遍历所有的属性,这就造成了如果 vue 对象的 data/computed/props 中的数据规模庞大,那么遍历起来就会慢很多
2、同理,如果 vue 对象的 data/computed/props 中的数据规模庞大,那么 Object.defineProperty 需要监听所有的属性的变化,那么占用内存就会很大
再来看 Proxy
Proxy 对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)
语法:
const p = new Proxy(target,handler)
参数:
1、target:需要使用Proxy包装的目标对象(可以使任何类型的对象,包括原生数组,函数,甚至可以使一个代理)。
2、handler:一个对象,其属性是当执行一个操作是定义代理的行为的函数(可以理解为某种触发器)。具体的handler相关函数可以查询官网 handler相关函数
大家老看一个 proxy 例子
const obj = {
name: 'Leo',
age: 26,
others: {
mobile: 'mi10',
watch: 'mi4'
}
}
const p = new Proxy(obj, {
get(target, key, receiver) {
console.log('查看的属性为:' + key);
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
console.log('设置的属性为:' + key);
console.log('新的属性:' + key, '值为:' + value);
Reflect.set(target, key, value, receiver);
}
})
p.age = 22
console.log(p.age)
p.single = 'NO'
console.log(p.single)
p.others.shoe = 'boost'
console.log(p.others.shoe)
有上面可以知道,新增或更新属性并不会重新添加响应式处理,一样能坚挺到,因为Proxy是对对象的操作,会走到Proxy的逻辑中。
Proxy和Object.defineProperty()的区别
Object.defineProperty()
在data中定义数据:如 {num:3}, 是根据具体的属性去对get和set进行拦截:
Object.defineProperty(data, 'num', {
get() {},
set() {},
})
Proxy
Proxy 不需要关心key,它去拦截的是修改 data 上的任意 key 和 读取 data 上的任意 key
所以,不管是已有的 key 还是新增的 key,都会监听到
但是 Proxy 更加强大的地方还在于 Proxy 除了 get 和 set,还可以拦截更多的操作符