再不看Vue源码就迟了 --- Vue.nextTick

567 阅读4分钟

该是时候开始行动了

Vue3.0beta了离正式发布还有一段时间,趁着还没发布3.0感觉熟悉一波,以不至于3.0发布之后更看不动了。
我们需要fork一份代码,并下载下来,延着package.json的入口进行寻找需要查看的源码。
(ps:怎么寻找源码入口可以去google一下)

一起看代码

接下来,请各位同学翻到 /src/core/util/next-tick.js

首先出现在我们画面里面的为 flushCallbacks 函数运行起来步骤如下:

1.设置pendingfalse
2.常量copies接收来自callbacks.slice(0)的返回
3.把数组callbacks设为空数组
4.遍历copies执行函数

中间的callbacks.slice(0)这一步或许有些同学看不懂,为此我来举个例子:

    const tryArr = [1,2,3];
    const newArr = tryArr.slice(0); // newArr = [1,2,3]
    tryArr.length = 0; // tryArr = []

tryArr.slice(0) 截取的是从数组下标0开始项,并返回赋值给newArr

接着我们继续往下看(ps:英文注释因为截图的原因被我删除了)

这里的源码对兼容性进行了判断

1.Promise
2.MutationObserver
3.setImmediate
4.setTimeout

我们首先看看Promise里面它做了什么

第一步先声明了一个类型为Promise的p变量

const p = Promise.resolve() // Promise {<resolved>: undefined}

接着为函数赋值并给p.then设置为flushCallbacks函数,以及在IOS会导致Promise.then不会完全中断,所以需要做一些其他工作,例如处理计时器,因此我们可以“强制”通过添加空计时器来刷新微任务(microtask)队列。(noop根据路径找到后为一个空函数)

MutationObserver()接收一个回调函数,它会在指定的DOM发生变化时被调用. 而要触发DOM的变化可以用到了new MutationObserver().observe函数,它接收一个DOM,以及config配置,其中characterData表示DOM节点的值更改时是否调用的回调函数。

这两个一起来讲,setImmediate其实用的是宏任务进行的,但不管怎么样它都比setTimeout更具有优势。

最后这里为nextTick的正文,通过上面的源码查看我们以及很容易的就能看出来它的运行机制, 会把接收过来的回调push到callbacks数组以等待执行,而pending则表示了运行的状态,当前是否还要函数未执行完成,若pending=false,则运行上面根据判断兼容的函数timerFunc。
至此关于nextTick我们已经看完了,但是有的同学可能还是不怎么懂为什么,那接着就来说说。

宏任务微任务

我们先来看一个例子:

console.log('开饭啦,我是第一位')

async function eatFoot() {
  await eatFoot2();
  console.log('我是第二位');
}

eatFoot();

new Promise(resolve => {
  console.log('我是第三位');
  resolve();
}).then(()=>console.log('加一位'))

async function eatFoot2() {
  console.log('我是第四位');
}

它们执行后的顺序为:开饭啦,我是第一位 -> 我是第四位 -> 我是第三位 -> 加一位 -> 我是第二位。 这是为什么呢?我们来看看它们的执行顺序:

1.首先执行了第一位的console
2.接着执行eatFoot遇到 await eatFoot2这里await表示为让出线程的意思,打印第四位
3.这时同步代码执行完后就该到异步代码了,所以执行到了Promise打印第三位
4.因为执行了resolve所以又进入了微任务的环境就走到了then里面,打印加一位
5.最后异步以及微任务没有了就返回回来await上面,打印第二位

这个顺序也就是Event Loop的执行顺序:

1.运行同步代码(宏任务 macrotask)
2.运行完所有同步代码后,再去看看有没有异步代码需要执行
3.运行所有微任务(microtask)
4.运行完所有微任务后,继续新的Event Loop,以及执行宏任务中的异步代码
到这里为止你觉得你可以知道下面这个最后为什么了吗

new Vue({
  data(){
      return {
        food: '等待吃饭中...',
      }
  },
  methods: {
    foodStatus() {
      this.food = "等一下"
      this.$nextTick(() => {
        this.food = '开饭'
      })
      this.food = '等一下'
    }
  }
  mounted(){
      this.foodStatus()
  }
})

小结

表示写作的功底还有待提升,以上所写的内容如有疑问欢迎留言,希望对各位兄弟有帮助!