本篇作为vue响应式原理的最后一篇,作为最后一个总结,比起我们helloworld的例子我们新的例子长这样:
template: <div id="app"> {{msg}} <div @click="change">{{test}}</div> </div> script: new Vue({ el:'#app', watch:{ msg:function(newer,old){ console.log(newer,old) } }, data:function(){ return{ msg: 'hello,world' } }, computed:{ test: function(){ return this.msg + '嗨,世界' } }, methods:{ change(){ this.msg = '10086' } } })
我们从点击事件发生的这一刻,开始追踪,当change事件发生后,首先会激活data里的msg上的set方法:
set: function reactiveSetter (newVal) { var value = getter ? getter.call(obj) : val; /* eslint-disable no-self-compare */ if (newVal === value || (newVal !== newVal && value !== value)) { return } /* eslint-enable no-self-compare */ if (customSetter) { customSetter(); } // #7981: for accessor properties without setter if (getter && !setter) { return } if (setter) { setter.call(obj, newVal); } else { val = newVal; } childOb = !shallow && observe(newVal); dep.notify(); }
此时,根据前文介绍,dep里的watcher应该有三个,第一个是watch的,第二个是组建的,第三个是computed的,然后,开始执行,这里注意到当一个响应式数据的值前后如果相等的话,set是会直接返回的,只有不相同的时候才回去执行下面的方法,也就是dep.notify:
Dep.prototype.notify = function notify () { // stabilize the subscriber list first var subs = this.subs.slice(); if (!config.async) { // subs aren't sorted in scheduler if not running async // we need to sort them now to make sure they fire in correct // order subs.sort(function (a, b) { return a.id - b.id; }); } for (var i = 0, l = subs.length; i < l; i++) { subs[i].update(); console.log(i) } }
一般我们的config.async都是true,表明我们组建的更新是异步的,然后执行每个warcher的update方法:
Watcher.prototype.update = function update () { /* istanbul ignore else */ if (this.lazy) { this.dirty = true; } else if (this.sync) { this.run(); } else { queueWatcher(this); } };
结合前面的知识可以清楚的知道,这里,computed上的watcher的this.lazy是true,因此这里在执行computed上watcher的update方法的时候,只会简单的将他的watcher的dirty标为true,而在执行watch和组建的watcher上的update方法的时候,会调用queueWatcher方法:
/** * Push a watcher into the watcher queue. * Jobs with duplicate IDs will be skipped unless it's * pushed when the queue is being flushed. */ function queueWatcher (watcher) { var id = watcher.id; if (has[id] == null) { has[id] = true; if (!flushing) { queue.push(watcher); } else { // if already flushing, splice the watcher based on its id // if already past its id, it will be run next immediately. var i = queue.length - 1; while (i > index && queue[i].id > watcher.id) { i--; } queue.splice(i + 1, 0, watcher); } // queue the flush if (!waiting) { waiting = true; if (!config.async) { flushSchedulerQueue(); return } nextTick(flushSchedulerQueue); } } }
queueWatcher在flush掉一个watcher之前,只会将同一个watcher往队列里添加一次,如果没有开始刷新队列,则将其push进队列,如果已经开始刷新了,就把他按从小到大的顺序添加到合适的位置,然后因为waiting默认值为false,开始走nextTick,next开始之前,注意到,这里传进去了一个函数,flushSchedulerQueue:
/** * Flush both queues and run the watchers. */ function flushSchedulerQueue () { currentFlushTimestamp = getNow(); flushing = true; var watcher, id; // Sort queue before flush. // This ensures that: // 1. Components are updated from parent to child. (because parent is always // created before the child) // 2. A component's user watchers are run before its render watcher (because // user watchers are created before the render watcher) // 3. If a component is destroyed during a parent component's watcher run, // its watchers can be skipped. queue.sort(function (a, b) { return a.id - b.id; }); // do not cache length because more watchers might be pushed // as we run existing watchers for (index = 0; index < queue.length; index++) { watcher = queue[index]; if (watcher.before) { watcher.before(); } id = watcher.id; has[id] = null; watcher.run(); // in dev build, check and stop circular updates. if (has[id] != null) { circular[id] = (circular[id] || 0) + 1; if (circular[id] > MAX_UPDATE_COUNT) { warn( 'You may have an infinite update loop ' + ( watcher.user ? ("in watcher with expression \"" + (watcher.expression) + "\"") : "in a component render function." ), watcher.vm ); break } } } // keep copies of post queues before resetting state var activatedQueue = activatedChildren.slice(); var updatedQueue = queue.slice(); resetSchedulerState(); // call component updated and activated hooks callActivatedHooks(activatedQueue); callUpdatedHooks(updatedQueue); // devtool hook /* istanbul ignore if */ if (devtools && config.devtools) { devtools.emit('flush'); } }
这个函数呢,字如其名,就是用来刷新队列的,在刷新队列之前,还将其进行了排序,为啥要排序,英文注释说的很清楚,保证其更新顺序由父到子,保证watch上的watcher更新在组建的watcher之前,当子组件在父组件watcher run的时候被destroy的时候可以跳过他的watcher,然后就开始遍历队列开始执行watcher的run方法了:
Watcher.prototype.run = function run () { if (this.active) { var value = this.get(); if ( value !== this.value || // Deep watchers and watchers on Object/Arrays should fire even // when the value is the same, because the value may // have mutated. isObject(value) || this.deep ) { // set new value var oldValue = this.value; this.value = value; if (this.user) { try { this.cb.call(this.vm, value, oldValue); } catch (e) { handleError(e, this.vm, ("callback for watcher \"" + (this.expression) + "\"")); } } else { this.cb.call(this.vm, value, oldValue); } } } };
run方法都会调用当前watcher上的get方法,而watcher的get方法里,会调用getter方法,这个方法就是用来收集依赖,以及更新视图的方法,在watch上的watcher是一个闭包函数,会访问一下他watch的vue实例上data里的响应式数据,进而激活他的get方法,在组建的watcher上是一个叫updateComponent方法,用于刷新视图,然后继续走到下面的步骤,如果是watch的watcher就执行ifelse分支的trycatch上的语句,此时就是执行的我们自定义的watch的回调。如果是其他的就执行else分支。
然后run方法执行完毕,回到flushSchedulerQueue 里,此时所有watcher执行完毕之后剩下的东西就是重置一下队列等等一系列后续工作。但是呢flushSchedulerQueue 在实际的运行的时候是在nextTick开始运行的,什么是nextTick我们放到下一章来说。