Observer构造函数中,最终执行了defineReactive为每一个属性进行定义,并且是递归调用,以树型遍历我们传入的data对象的所有节点属性,每一个节点都会被包装为一个观察者,当数据get时,进行依赖收集,当数据set时,事件分发。
看到这里,感觉好像少了点什么,好像data到这里就结束了,但是并没有看懂为什么数据改变更新视图的,那么继续往下看
vue挂载到dom回看的_init方法,在这个方法中,最后调用了vm.$mount(vm.$options.el),这是把vm挂载到真实dom,并渲染view的地方,因此接着看下去。
Vue.prototype.$mount = function ( el, hydrating ) { return mountComponent(this, el, hydrating) }; // 渲染dom的真实函数 function mountComponent ( vm, el, hydrating ) { vm.$el = el; callHook(vm, 'beforeMount'); var updateComponent; updateComponent = function () { vm._update(vm._render(), hydrating); }; // new 一个Watcher,开启了数据驱动之旅 new Watcher(vm, updateComponent, noop, { before: function before () { if (vm._isMounted) { callHook(vm, 'beforeUpdate'); } } }, true /* isRenderWatcher */); hydrating = false; if (vm.$vnode == null) { vm._isMounted = true; callHook(vm, 'mounted'); } return vm }上面部分看到的是,vue将vue对象挂载到真实dom的经历,最终执行了new Watcher,并且回调为vm._update(vm._render(), hydrating)。顾名思义,这里是执行了vue的更新view的操作(本文暂且不讲更新view,在其他文章已经讲过。本文专注数据驱动部分)。
问:为什么说new Watcher开启了数据驱动之旅呢?Watcher又是什么功能?
简述Watcher如果说Object.defineProperty是vue数据驱动的灵魂,那么Watcher则是他的骨骼。
// 超级简单的Watcher var Watcher = function Watcher ( vm, expOrFn, cb, options, isRenderWatcher ) { this.cb = cb; this.deps = []; this.newDeps = []; // 计算属性走if if (this.computed) { this.value = undefined; this.dep = new Dep(); } else { this.value = this.get(); } }; Watcher.prototype.get = function get () { pushTarget(this); var value; var vm = this.vm; try { value = this.getter.call(vm, vm); } catch (e) { if (this.user) { handleError(e, vm, ("getter for watcher \"" + (this.expression) + "\"")); } else { throw e } } finally { popTarget(); this.cleanupDeps(); } return value };简化后Watcher在new时,最终会调用自己的get方法,get方法中第一个语句pushTarget(this)是开启数据驱动的第一把钥匙,看下文
function pushTarget (_target) { if (Dep.target) { targetStack.push(Dep.target); } Dep.target = _target; }pushTarget将传入的Watcher对象赋值给了Dep.target,还记得在讲Object.defineProperty时提到了,Dep.target.update是更新view的触发点,在这里终于找到了!
下面看Dep.targe.update
Watcher.prototype.update = function update () { var this$1 = this; /* istanbul ignore else */ if (this.computed) { if (this.dep.subs.length === 0) { this.dirty = true; } else { this.getAndInvoke(function () { this$1.dep.notify(); }); } } else if (this.sync) { this.run(); } else { // update执行了这里 queueWatcher(this); } };我们看到update方法最后执行了queueWatcher,继续看下去发现,这其实是一个更新队列,vue对同一个微任务的所有update进行了收集更新,最终执行了watcher.run,run方法又执行了getAndInvoke方法,getAndInvoke又执行了this.get方法。
到来一大圈,终于找到:在改变属性值时,触发了Dep.target所对应的Watcher的 this.get方法,this.get方法其实就是传入进来的回调函数。回想前面介绍的,vue在挂载到真实dom时,new Watcher传入的回调是updateComponent。串联起来得到了结论:
我们在get属性时,Dep派发器收集到了Watcher当作依赖
当我们set属性时,Dep派发器事件分发,使所有收集到的依赖执行this.get,这时候view会更新。
到这里,有没有明白为什么所有属性的派发器都会收集updateComponent的Watcher,从而在自己set时通知更新?如果没明白,那就看下一节分析
从宏观角度看问题当我们new Vue时,首先会将传入的data将被vue包装为观察者,所有get和set行为都会捕捉到并执行响应的操作
接下来vue会将vue对象挂载到真实dom(其实指的虚拟的渲染),这个时候new 一个Watcher, 在new Watcher时,会执行一次this.get初始化一次值,对标updateComponent函数,这个时候会触发vue渲染过程
在vue渲染过程中,所有的数据都需要进行get行为才能得到值并给真实dom赋值,因此这时触发了所有data属性的get,并且此时Dep.target是updateComponent的Watcher,因此所有的data属性派发器都收集到了此Watcher,在set时,派发器notify进行事件分发,收集到的依赖Watcher都得到了通知进行update,所有又会执行updateCompoent进行更新view
通过案例进行分析 vue数据驱动的前提vue数据驱动是有前提条件的,不是怎么用都可以的,前提条件就是必须在data中声明的属性才会参与数据驱动,数据->视图。看下面栗子
有如下html:
<div> <div>{{prev}}{{next}}</div> </div>如下js:
new Vue({ el: "#app", data: { prev: 'hello', }, created() { }, mounted() { this.next = 'world'; } })页面渲染的结果是什呢?
答:hello;