vue添加数据劫持的过程&依赖的收集与响应
添加数据劫持
首先数据劫持是加在data上的。
vue递归侦测数据的属性,是通过几个函数交替循环执行的:
observe函数:负责判断对象参数是否有“_ob_”属性,没有就new Observer()创建实例并赋值。
Observer:类,主要做2件事
- 构造函数中给当前初始化传入的对象添加“_ob_”属性,值就是当前Observer实例
- 构造函数中调用walk函数,遍历对象属性并添加数据劫持
walk函数:就是负责循环遍历对象属性,过程中会调用defineReactive函数给属性添加数据劫持。
defineReactive函数:负责利用Object.defineProperty方法给对象添加get set拦截。
并调用observe函数继续给对象属性添加Observer实例。
每次触发set的时候最后也会调用observe函数给更新的属性添加劫持。
自此,递归的循环调用链完成:observe -> _ob_ -> walk -> defineReactive -> observe
递归结束后,data的所有属性都会被设置数据劫持,并附加Observer的实例,这个附加的_ob_也是为了方便依赖的收集和响应。
依赖收集和响应
个人理解“依赖”就是对data数据的关联关系,包括对数据读写的响应,这种关系被封装到Watcher实例中。
依赖的收集自然离不开跟踪data的每个属性,Dep实例就是做这个一线收集和反馈工作的,Dep实例被封装到_ob_中,被命名为dep属性。
dep和watcher的通讯使用了发布订阅的设计模式,对data某一属性的监听可能会有多个,所以dep作为发布者,内部维护一个订阅者列表,watcher作为订阅者,负责实现回调。并且watcher内部也维护了一个发布者的map,是为了防止重复订阅。
“在getter中收集依赖,在setter中响应依赖”:
每次只要读取到data的属性,就会触发getter方法,getter方法除了返回值,还会调用dep.depend()方法收集依赖:
这个方法会检查当前属性有没有watcher实例监听,如果有的话,就会把当前dep实例传给watcher实例,watcher实例存好这个dep实例后,也会调dep实例的方法把watcher实例存到dep实例内部的一个列表中。
至于为什么dep会知道是否有watcher实例监听:在创建watcher实例的时候,watcher实例会把自己赋值到全局变量Dep.target,并读取一次当前绑定的data属性,随之就会触发这个data属性的getter方法,于是触发dep.depend(),dep实例在收集依赖的时候就会首先去判断Dep.target,有值就表明有watcher在监听,然后就进行依赖收集。
每次更新data的属性的时候,就会触发setter方法,setter方法内部调用了dep.notify(),通知watcher去执行回调,并且通知组件执行patch函数(即对vnode使用diff算法重新渲染dom)