先说我的看法,就是为了 防止子组件重复渲染
看看下面很简单的代码
App.vue
<template>
<div id="app">
<div>
a:{{ a }}
<HelloWorld1 :a="a" @changea="changea"> </HelloWorld1>
<HelloWorld2 :a="a"> </HelloWorld2>
</div>
</div>
</template>
<script>
import HelloWorld1 from "./components/HelloWorld1.vue";
import HelloWorld2 from "./components/HelloWorld2.vue";
export default {
name: "App",
components: {
HelloWorld1,
HelloWorld2,
},
data() {
return {
a: 0,
};
},
updated() {
console.log("APP update");
},
methods: {
changea() {
debugger;
this.a++;
},
},
};
</script>
HelloWorld1.vue
<template>
<div>
<button @click="click">Change b</button>
<div>HelloWorld1-a:{{ a }}</div>
<div>HelloWorld1-b:{{ b }}</div>
</div>
</template>
<script>
export default {
name: "HelloWorld1",
props: ["a"],
data() {
return {
b: 0,
};
},
updated() {
console.log("HelloWorld1 update");
},
methods: {
click() {
debugger;
this.b = this.b + 1;
this.$emit("changea", this.b);
},
},
};
</script>
Helloword2.vue
<template>
<div>HelloWorld2-a:{{ a }}</div>
</template>
<script>
export default {
name: "HelloWorld2",
props: ["a"],
data() {
return {};
},
methods: {
click() {
debugger;
this.a++;
},
},
};
</script>
1.Helloword1和Helloword2都是App的子组件
2.点击Helloword1的'Change b'按钮,Helloword1组件里面的b会+1,同时会将更新后的b通过emit("changea",this.b)传给父组件App,改变App的a,
3.App的a作为prop同时传给Helloword1和Helloword2这两个组件。
上面过程中,可以总结为点下按钮会直接或间接导致三件事情的发生(渲染watcher也就是vnode -> 真实dom的过程)
- 改变b直接触发Helloword1的渲染watcher
- b导致a改变,a触发App的渲染watcher
- App执行渲染watcher的过程中,updateChildComponent更新子组件的过程中,也触发了子组件"props[a]"的值(
props[key] = validateProp(key, propOptions, propsData, vm);)所以Helloword1和Helloword2会执行各自的渲染watcher
说以下内容的时候,首先要说明几个知识点(这里不讨论computed和watcher,假设所有组件都只有渲染watcher)
1.上面说的执行渲染watcher并不是立即执行的,而是将他们维护到一个队列queue中,在下一次微任务(大多数)执行
2.如果在队列执行过程中(在这个例子里可以理解为某个组件a的渲染watcher正在执行),如果又触发了其他组件的渲染函数**(假设为b组件),这时候会比较b组件和a组件的渲染watcher的id,如果b的比较大,则直接插到queue的队尾,否则会等待下一次微任务再执行
3.插入队列的过程中,如果遇到相同id的watcher,会移除重复,只留下一个该id的watcher
flushSchedulerQueue是依次执行queue里渲染watcher的方法
现在假设(我debugger出来是这几个id,但无论id是什么,id的大小终归是父组件>子组件)
App组件的渲染watcher的id是2
Helloword1组件的渲染watcher的id是3
Helloword2组件的渲染watcher的id是4
假如flushSchedulerQueue没有对watcher队列进行从小到大排序
注释排序代码
// queue.sort(function (a, b) { return a.id - b.id; });
-
改变b直接触发Helloword1的渲染watcher,queue为[3]
-
b导致a改变,a触发App的渲染watcher,queue为[3,2](APP组件在执行渲染watcher的过程中才因为props触发子组件的渲染watcher)
-
flushSchedulerQueue执行,按照[3,2]的顺序
首先,Helloword1的渲染watcher(id=3)执行完成(执行了一次),队列中认为没有这个id的watcher了(后面有相同id的watcher不会认为存在重复)
接着,App的渲染watcher(id=2)执行,触发了HelloWorld1和HelloWorld2的渲染watcher
所以,queue会push进id=3,id=4的两个渲染watcher,那么id=3和id=4渲染watcher会执行(执行了第二次)
从上面可以看出来,id=3的watcher执行了两边,也就是说HellowWorld1的组件在一次点击按钮过程中,会更新两次
flushSchedulerQueue对watcher队列进行从小到大排序
-
改变b直接触发Helloword1的渲染watcher,queue为[3]
-
b导致a改变,a触发App的渲染watcher,queue为[3,2](APP组件在执行渲染watcher的过程中才因为props触发子组件的渲染watcher)
-
flushSchedulerQueue执行,id从小到大排序,按照[2,3]的顺序
首先,App的渲染watcher(id=2)执行,触发了HelloWorld1和HelloWorld2的渲染watcher
接着,queue已经有id=3的watcher,所以不会push进id=3,只会push id=4的这个渲染watcher,现在queue里面是[2,3,4]
从上面可以看出来,id=3的watcher只执行了一次