Array的变化侦测
为什么数组和对象的侦测方式不同?
可能很多人不理解为什么Array的侦测方式和Object的不同,举例说明:
this.list.push(1)
如上,我们是使用push方法向list中新增了数字1。之前介绍Object的时候,其侦测方式是通过getter/setter实现的,但是数组是通过使用push方法来改变数组,就不能触发getter/setter。
如何追踪数组变化
Object是的变化是靠追踪setter,只要数据发送变化,就会触发setter。
同理,数组通过方法来更改,只要在用户使用方法的操作数组的时候得到通知,就能实现同样的目的。
这样通过拦截器,我们就可以追踪到Array的变化。
拦截器
拦截器就是一个和Array.prototype一样的Object,里面包含的属性一模一样,只不过这个Object中的方法是我们处理过的。
那我们来处理一下:
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
['push','pop','shift','unshift','splice','sort','reverse'].forEach(method => {
//缓存原始方法
const original = arrayProto[method]
Object.defineProperty(arrayMethods, method, {
value: function mutator(...args) {
return original.apply(this, args)
},
enumerable: false,
writable: true,
configurable: true
})
});
在上面代码中,我们将所有的数组方法通过Object.prototype进行封装,所以当使用push方法的时候,其实使用的是arrayMethods.push,而arrayMethods.push是mutator函数,在mutator函数中执行original来做它该做的事,例如push。所以我们就可以在mutator中做一些其他的事,比如发送通知
使用拦截器覆盖Array原型
有了拦截器,就要让他生效,所以需要去覆盖Array.prototype。但是不能直接覆盖,直接覆盖会污染全局的Array。我们只希望拦截操作只针对那些被侦测了变化的数组,也就是只覆盖这一部分的响应式数组的原型。
而将一个数据转换为响应式,需要通过Observer(vue响应式原理有讲,https://www.cnblogs.com/taosifan/p/15329016.html),所以只需要在Observer中使用拦截器覆盖这些数组的原型就好了:
export class Observer {
constructor (value) {
this.value = value
if(!Array.isArray(value)) { //判断是不是数组,数组需要单独进行特殊处理
this.walk(value)
} else {
value.__proto__ = arrayMethods //覆盖原型
}
}
好了,现在的结构就变成了这样:
将拦截器直接挂载到数组的属性上
对于有些不支持__proto__的情况,我们可以将arrayMethods身上的方法设置到被侦测的数组上:
export class Observer {
constructor(value) {
this.value = value
if(!Array.isArray(value)) {
this.walk(value)
} else {
const augment = hasProto ? protoAugment : copyAugment
augment(value, arrayMethods, arrayKeys)
}
}
}
function protoAugment(target, src, keys) {
target.__proto__ = src
}
function copyAugment(target, src, keys) {
for (let i in keys) {
const key = keys[i]
def(target, key, src[key])
}
}
因为当访问一个对象的方法时,只有其自身没有这个方法,才会去原型上找这个方法
如何收集Array的依赖
后面好多啊,我想鸽一下。结构先搭起
如何去通知watcher
如何监听添加或者删除
关于Array的问题
行百里者半九十