本篇作为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我们放到下一章来说。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号