为什么flushSchedulerQueue需要对watcher队列进行从小到大排序

849 阅读3分钟

先说我的看法,就是为了 防止子组件重复渲染

看看下面很简单的代码

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的过程)

  1. 改变b直接触发Helloword1的渲染watcher
  2. b导致a改变,a触发App的渲染watcher
  3. 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只执行了一次