vue2和vue3都重写了数组方法

vue2和vue3都重写了数组方法

vue2的defineProperty

1
2
3
4
5
6
7
8
9
10
11
12
13
function defineReactive(obj, key) {
let _val = obj[key];
Object.defineProperty(obj, key, {
get() {
console.log('get')
return _val;
},
set(newVal) {
console.log('set')
_val = newVal;
}
})
}

defineProperty利用闭包, 对_val进行set和get,称为数据劫持。

  • defineProperty的缺陷

由此可见defineProperty的响应式粒度是key级别的。也就是说,已知的属性才有办法劫持其getter和setter,所以其根本没有办法对未知的key劫持做响应。

  • 为什么vue2不对数组进行劫持?而是重写数组方法?

在vue2直接通过下标修改数组是不会响应式更新的。

1
2
arr[0] = 'xxx'  //×
this.$set(arr,0,'xxx') //√

原因:

  1. 数组如果很大,会产生大量闭包,耗费大量内存。
  2. 一般开发者只关心数组本身的变化,而不关心每个元素内部的变化。而这些数组方法恰好只关注数组本身的变化。
  3. 要对数组劫持的话,对于未知的key无法配置getter和setter。
  • 那为什么对象就劫持了呢?
    因为对象和数组关注点不同,数组自身可能变化;而对象自生一般不变化,关注属性的变化。

vue3的重写数组方法

基于Proxy实现响应式,其粒度在对象级别, 所以对于未知的key也能实现响应式。

1
2
3
4
5
6
7
8
9
10
11
12
function reactive(target) {
return new Proxy(target, {
get() {
console.log('get')
return Reflect.get(...arguments)
},
set() {
console.log('set')
return Reflect.set(...arguments);
}
})
}
  • vue3还要重写数组方法?
    因为利用Proxy返回的是代理对象,和原本对象不相等。所以需要重写一些查找相关的方法。
    但是vue3还重写了一些修改相关的方法。为什么,有必要吗?

因为调用push等方法会

  1. 访问push属性
  2. 访问length属性
  3. 修改length属性

当访问length属性也被收集做依赖,那么接下来修改length会导致此方法被无限调用

  • 而且Vue3代理是惰性的

不像defineProperty一样需要先遍历所有key,对所有key做劫持。而是读到该属性为对象时才会递归reactive

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function reactive(target) {
console.log('计数') //只打印一次
return new Proxy(target, {
get(target, prop) {
console.log('get', prop)
if (target[prop] instanceof Object)
return reactive(target[prop])
return Reflect.get(...arguments)
},
set() {
console.log('set')
return Reflect.set(...arguments);
}
})
}

const p = reactive({
name: 'lzy',
friend: {
name: 'xxx'
}
})