Vue中数组的响应式原理解析
目录Vue中数组的响应式原理解析实现思路拦截器将拦截器挂载到数组上收集依赖总结Vue中数组的响应式原理解析为何Vue中对象与数组的侦测方式不同?因为数组的一些方法如push、pop等不会触发getter/setter。为何这些方法不会触发getter/setter?因为这些方法是Array的原型上的方法,并没有在Array本身上面。如何做?使用拦截器覆盖Array.prototype上的方法,在执
·
Vue中数组的响应式原理解析
为何Vue中对象与数组的侦测方式不同?因为数组的一些方法如push
、pop
等不会触发getter/setter
。
为何这些方法不会触发getter/setter
?因为这些方法是Array
的原型上的方法,并没有在Array
本身上面。
如何做?使用拦截器覆盖Array.prototype
上的方法,在执行原型上的方法之外做数据的响应式。
实现思路
- 将数组的原型存到对象
arrayMethods
中 - 找到Array上能够改变数组自身的7个方法
push
,pop
,shift
,unshift
,splice
,sort
,reverse
- 将这7个方法进行响应式处理
- 处理完成后,用它们把
arrayMethods
中对应的方法覆盖掉 - 将需要进行响应式处理的数组
arr
的__proto__
指向arrayMethods
,如果浏览器不支持访问__proto__
,则直接将响应式处理后的7个方法添加到数组arr
上 - 如果要将一个数组完全实现响应式,需要遍历该数组,将数组中的
数组
使用该方法进行响应式处理,将对象
使用walk方法进行响应式处理
拦截器
const arrayProto = Array.prototype;
export const arrayMethods = Object.create(arrayProto);
[
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
.forEach(mentod => {
// 缓存原始方法
const original = arrayProto[method];
Object.defineProperty(arrayMethods, method, {
value: function mutator(...args) {
// 返回原始方法
return original.apply(this, args);
},
enumerable: false,
writable: true,
configurable: true
})
})
将拦截器挂载到数组上
import { arrayMethods } from './array' // 处理好的Array原型对象
// __proto__是否可用
const hasProto = '__proto__' in {};
// 所有属性名,不论是否可枚举(与Object.keys的区别)
const arrayKeys = Object.getOwnPropertyNames(arrayMethods);
export class Observe {
// 将value转为响应式
constructor (value) {
this.value = value;
if (Array.isArray(value)) {
const augment = hasProto ? protoAugment : copyAugment;
augment(value, arrayMethods, arrayKeys);
} else {
this.walk(value); // Object的响应式处理,在其他文章中
}
}
}
/**
* __proto__可用的处理方式
* 将target对象的原型对象替换为src
*/
function protoAugment(target, src, keys) {
target.__proto__ = src;
}
/**
* __proto__不可用的处理方式
* 将src上面的所有属性都copy到target
*/
function copyAugment (target, src, keys) {
for (let i = 0, len = keys.length; i < len; i ++) {
const key = keys[i];
def(target, key, src[key]);
}
}
// Object.defineProperty()的封装
function def (obj, key, val, enumerable) {
Object.defineProperty(obj, key, {
value: val,
enumerable: !!enumerable,
writable: true,
configurable: true
})
}
收集依赖
function defineReactive(data, key, val) {
let childOb = observe(val);
let dep = new Dep(); // 存储依赖
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function () {
dep.depend();
if (childOb) childOb.dep.depend(); // 收集
return val;
},
set: function (newVal) {
if (val === newVal) return;
dep.notify();
val = newVal;
}
})
}
// 返回val的响应式对象
function observe(val, asRootData) {
if (!isObject(value)) return;
let ob;
// 避免重复侦测
if (hasOwn(value, '__ob__') && value.__ob__ instanceof observer) {
ob = value.__ob__;
} else {
ob = new Observe(value)
}
return ob;
}
问题
有些数组的操作Vue 2.0是拦截不到的:
-
this.list[0] = 2;
-
this.list.length = 0;
上面两种情况不会触发响应式,因为Vue 2.0的实现方式决定了无法对上面种种情况做拦截。
在Vue 3.0中使用Proxy解决了这个问题。
总结
Array在getter中收集依赖,在拦截器中触发依赖,在Observer中保存依赖
更多推荐
已为社区贡献7条内容
所有评论(0)