文章围绕下面demo进行分析vue
<div id="app"> <span>{{a.b}} {{c}} {{d}}</span> </div> <script> var app = new Vue({ el: "#app", data: function(){ return { a: { b: 1 }, c: 1 } }, watch:{ 'a.b': function(){ console.log(22) } }, computed:{ d: function (){ return this.c } } }); </script>
vue监听data数据的步骤能够归纳为下面几步:node
一、使用initData初始化data数据 二、将data挂载到vm._data中(后面会将_data赋值给$data,这里的data若是是函数,就是返回的数据) 三、经过observe监听数据 四、若是数据是一个非object类型的数据(typeof == object,且不为null),终止程序 五、observe中使用new Observer生成ob 六、Observer中使用`this.dep = new Dep()`挂载dep 七、Observer中将__ob__指向this,因此可使用`__ob__.dep`找到dep 八、遍历属性,使用defineReactive监听属性 九、defineReactive中生成`var dep = new Dep();`,这个dep是一个闭包 十、defineReactive中使用observe监听属性所表明的值,也就是步骤3,至此循环递归
三种watcherexpress
一、normal-watcher(watch中的数据,经过initWatch生成) 记录在_watchers中(全部的watch都会存放在这里) 二、computed-watcher(computed) 记录在_computedWatchers中 三、render-watcher 就是vm._watcher
咱们在组件钩子函数watch 中定义的,都属于这种类型,即只要监听的属性改变了,都会触发定义好的回调函数,这类watch的expression是计算属性中的属性名。闭包
在初始化watch的时候(initWatch),会调用vm.$watch函数,vm.$watch函数会直接使用Watcher构建观察者对象。watch中属性的值做为watcher.cb存在,在观察者update的时候,在watcher.run函数中执行。watch中属性的key会进行parsePath处理,并用parsePath返回的函数,获取watch的初始值value。app
好比把a.b解析为监听a中的b属性,这样会先寻找a,也就是触发a的get。函数
一、赋值cb为a.b的值,赋值expression为a.b 二、使用parsePath解析expOrFn,即a.b,并将返回的函数赋值给该watcher实例的getter即this.getter 三、运行this.get获取a.b的值,进行依赖收集,this指向a.b的 watcher实例 四、运行pushTarget将Dep.target指向该watcher实例 五、运行this.getter,会先获取a,运行defineReactive中的get 六、运行dep.depend(此时的dep指的是data.a的dep,在闭包中),进而运行Dep.target.addDep,将data.a的dep追加进该watcher实例中,并将该watcher实例追加进data.a的dep.subs中,由于a具备__ob__,因此会运行a.__ob__.dep.depend,将a的dep追加进该watcher实例中,并将该watcher实例追加进a的dep.subs中 七、利用获取到的a去获取属性b 八、运行dep.depend(此时的dep指的是a.b的dep,在闭包中),进而运行Dep.target.addDep,将a.b的dep追加进该watcher实例中,并将该watcher实例追加进a.b的dep.subs中,由于b不具备__ob__,因此不会继续追加 九、到这里就获取到了a.b的值1,并将这个值赋值给该watcher实例的value
咱们在组件钩子函数computed中定义的,都属于这种类型,每个 computed 属性,最后都会生成一个对应的 watcher 对象,可是这类 watcher 有个特色:当计算属性依赖于其余数据时,属性并不会当即从新计算,只有以后其余地方须要读取属性的时候,它才会真正计算,即具有 lazy(懒计算)特性。这类watch的expression是计算属性中的属性名。this
在初始化computed的时候(initComputed),会先生成watch实例,而后监测数据是否已经存在data或props上,若是存在则抛出警告,不然调用defineComputed函数,监听数据,为组件中的属性绑定getter及setter。lua
注意:computed中的属性是直接绑定在vm上的,因此若是写a.d,那就是属性名是a.d,而不是a对象的属性d。spa
一、执行initComputed,遍历computed生成watch实例,并挂载到vm._computedWatchers上 (1)赋值cb为空函数,赋值expression为expOrFn(d的值,函数或对象的get)的字符串形式,赋值this.getter为expOrFn (2)默认的computed设置lazy为true,不运行this.get获取值,因此到这里watch实例就生成了。 二、执行defineComputed函数,若是d的值是函数,或者d的cache属性不是false,那么会使用createComputedGetter函数生成computedGetter函数,做为d的getter函数,若是cache设置为false,不通过createComputedGetter封装,每次获取都会运行get,而d的setter就是他的set或者空函数(默认) 三、当获取d的值时(好比渲染,此时Dep.target为渲染watcher),会运行computedGetter函数 四、根据watcher.dirty的值决定是否运行watcher.evaluate从新获取属性值,这是懒计算的关键。dirty的值默认为true,在依赖改变时或update时变为true,在evaluate后变为false (1)watcher.evaluate中运行this.get获取d的值,进行依赖收集,this指向d的 watcher实例 (2)运行pushTarget将Dep.target指向d的watcher实例 (3)运行this.getter,会先获取this.c的值,运行defineReactive中的get (4)运行dep.depend(此时的dep指的是data.c的dep,在闭包中),进而运行Dep.target.addDep,将data.c的dep追加进d的watcher实例中,并将d的watcher实例追加进data.c的dep.subs中 (5)d的watcher出栈,将Dep.target从新设置为渲染watcher 五、运行watcher.depend,遍历watcher.deps(这里主要是data.c的dep),将他们与渲染watcher互相关联
注意:computed中的数据不通过Observer监听,因此不存在depcode
每个组件都会有一个 render-watcher, 当 data/computed 中的属性改变的时候,会调用该 render-watcher 来更新组件的视图。这类watch的expression是 function () {vm._update(vm._render(), hydrating);}
。
一、生成updateComponent函数, 二、实例化一个渲染watcher,把updateComponent看成expOrFn参数传入 三、赋值cb为空函数,赋值expression为updateComponent的字符串形式,赋值this.getter为expOrFn 四、运行this.get,进行依赖收集 五、运行pushTarget将Dep.target指向该渲染watcher实例 六、运行this.getter,即updateComponent函数 七、用render函数生成vnode,并将其做为第一个参数,传入_update 八、render函数中会对用到的变量进行getter操做,并完成依赖收集 (1)获取a,将data.a的dep追加进该渲染watcher实例中,并将该渲染watcher实例追加进data.a的dep.subs中 (2)获取a.b,将a.b的dep追加进该渲染watcher实例中,并将该渲染watcher实例追加进a.b的dep.subs中 (3)获取c,将data.c的dep追加进该渲染watcher实例中,并将该渲染watcher实例追加进data.c的dep.subs中 (4)获取d,运行d的getter函数computedGetter(详情看上面computed-watcher中的步骤3-5) 九、完成依赖收集后,变量修改,会触发dep.notify,通知渲染watcher实例的update操做,从新进行渲染