Vue2.0中的$watch$set$delete源码解析
Posted 登楼痕
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Vue2.0中的$watch$set$delete源码解析相关的知识,希望对你有一定的参考价值。
$watch的实现就是对Watcher类的封装,在此基础上实现了deep: true, immediate: true可选参数。 在实际使用中,expOrFn这个参数可以是一个表达式或者是一个函数,表达式只接受以点分隔的路径,比如“a.b.c”。所以当参数是一个函数的时候,Watcher类的构造器里面会直接把该函数赋值给this.getter = expOrFn,否则,就用parsePath函数去解析表达式后赋值给getter。 值得注意的是,如果参数是函数,那么Watcher会同时观察这个函数里读取的实例上的数据,任一数据变动都会得到通知。export default class Watcher constructor(vm, expOrFn, cb) this.vm = vm; if(typeof expOrFn === 'function') this.getter = expOrFn; else this.getter = parsePath(expOrFn) this.cb = cb; this.value = this.get(); ......immediate参数存在,则直接立即执行一次cb,这个很简单。 最后unwatchFn顾名思义就是把当前watcher实例从正在观察的依赖列表中移除。怎么在Watcher类里实现需要的时候添加,不需要的时候移除呢? 就是用一个let depIds = new Set()集合来判断,如果当前Watcher已经订阅了Dep,则不会重复添加订阅。这个判断,可以防止数据变化时Watcher读取新数据时,重复收集依赖。
export default class Watcher constructor(vm, expOrFn, cb) this.vm = vm; this.deps = []; this.depIds = new Set(); ...... this.cb = cb; this.value = this.get(); ...... addDep(dep) const id = dep.id; if(!this.depIds.has(id)) this.depIds.add(id); // 记录自己以后订阅了 this.deps.push(dep); // 记录自己订阅了哪些Dep dep.addSub(this); // 将自己订阅到Dep中 ......在Dep类里面,也会通过window.target.addDep(this)来记录数据发生变化时,要通知哪些Watcher, 所以Watcher和Dep是多对多关系。 那么teardown()做的事就是遍历deps,然后执行this.deps[i].removeSub(this),removeSub()函数就是将Dep里的Watcher从subs数组中移除,所以数据发生变化时,就不会再通知这个Watcher了。 最后是deep参数的实现逻辑,我们知道deep是深度监听变化,也就是不仅当前数据需要收集依赖,其子数据也要触发依赖收集。那么就在get()函数里, 在window.target=undefined之前,进行递归遍历。递归逻辑很简单。如果不是数组或者对象,或者已经被冻结,那么直接返回什么都不做;接着判断dep.id是否存在,来保证不会重复收集依赖;如果是数组,则遍历数组元素递归调用;如果是Object类型,则通过循环key,递归子值,此时因为会获取子值所以会触发一次getter,也就是触发依赖收集,此时window.target没有清空,所以会执行收集,这也是为什么要写在清空逻辑之前的原因。 vm.$set(target, key, value) 对于新增的对象属性无法被追踪到,所以$set函数主要是用来规避这个问题,使新增的属性变为响应式。set实现源码如下: 首先看对target是数组的处理: 如果索引是有效值,那么先更改length,然后利用splice方法设置对应的值,在执行splice方法的时候,数组拦截器会侦听到数据变化,从而把新增的val转换成响应式,然后返回val。 如果key已经存在于target,那么直接改value就行了,修改的动作直接就会被侦听到,然后通知依赖。 如果key是新增的,首先剔除target是vue实例或者根数据对象,然后判断是否有_ob_属性,没有的话说明不是响应式的,那么直接target[key]=val。如果都不满足,说明是在响应式数据上新增属性,就用defineReactive函数转换为getter/setter形式就行。 vm.$delete(target, key) ES6之前,js无法检测到属性从对象上删除,所以使用$delete方法,在删除后想依赖发送消息,通知Watcher数据变化了。这个方法就定义在set函数下面: 看代码就行,其实大意就是删掉后,然后主动触发依赖更新。
以上是关于Vue2.0中的$watch$set$delete源码解析的主要内容,如果未能解决你的问题,请参考以下文章
Vue2.0学习—watch和computed对比(三十七)