<div id="example">
<div>{{ words }}</div>
<input type="button" @click="clickHanler" value="click"/>
</div>
var vm = new Vue({
el:"#example",
data: {
name: "Devin",
greetings: "Hello"
},
computed: {
words: function(){
return this.greetings + ' ' + this.name + '!'
}
},
methods: {
clickHanler(){
this.name = 'Devinn';
this.name = 'Devinnzhang';
this.greetings = 'Morning';
}
}
});
此时 Vue 中创建了两个 watcher,一个是渲染 watcher,负责渲染模板,一个是 computed watcher 负责计算属性。当点击 clickHandler 的时候数据发生变化会通知道两个 watcher ,watcher进行更新。这里的 watcher 更新会有两个问题:
1、渲染watcher 和 computed watcher 同时订阅了变化的数据,哪一个先执行,执行顺序是怎么样的?
2、在一个事件循环中 name 的变化触发了两次,greetings 触发了一次,对应两个 watcher 一共执行了几次?DOM渲染了几次?
原理:
当数据发生变化时,会调用 dep.notity() 进而通知订阅的 watcher 进行 更新
Watcher.prototype.update = function update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true;
} else if (this.sync) {
this.run();
} else {
queueWatcher(this);
}
};
可以看到 update 中 watcher 并没有立即执行( 同步的除外),而是调用了 queueWatcher (将更新的 watcher 加入到了一个队列中),看下 queueWatcher 的实现:
function queueWatcher (watcher) {
var id = watcher.id;
console.log('watcherId='+ id + 'exporession=' + watcher.expression);
if (has[id] == null) {
//console.log('watcherId='+ id + 'exporession=' + watcher.expression);
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 做了两个事:
1、将 watcher 压入队列,重复的 watcher 知会被压入一次,这样在一个事件循环中 触发了多次的 watcher 只会被压入队列一次。如例子中异步队列中只有一个 渲染 watcher 和一个computed watcher;
2、调用 nextTick(flushSchedulerQueue) 对队列中的 watcher 进行异步执行, nextTick 实现异步,flushSchedulerQueue 对 watcher 进行遍历执行。