Cosen95 / fe_interview

字节、阿里、美团、滴滴、腾讯等大厂高级前端面试题整理
239 stars 25 forks source link

谈一下 nextTick 的原理 #69

Open Cosen95 opened 4 years ago

Cosen95 commented 4 years ago

Vue.js在默认情况下,每次触发某个数据的 setter 方法后,对应的 Watcher 对象其实会被 push 进一个队列 queue 中,在下一个 tick 的时候将这个队列 queue 全部拿出来 runWatcher 对象的一个方法,用来触发 patch 操作) 一遍。

因为目前浏览器平台并没有实现 nextTick 方法,所以 Vue.js 源码中分别用 PromisesetTimeoutsetImmediate 等方式在 microtask(或是task)中创建一个事件,目的是在当前调用栈执行完毕以后(不一定立即)才会去执行这个事件。

nextTick方法主要是使用了宏任务和微任务,定义了一个异步方法.多次调用nextTick 会将方法存入队列中,通过这个异步方法清空当前队列。

所以这个 nextTick 方法是异步方法。

通过一张图来看下nextTick的实现:

nextTick实现原理

if (typeof Promise !== 'undefined' && isNative(Promise)) { timerFunc = () => { // ... } isUsingMicroTask = true } else if (!isIE && typeof MutationObserver !== 'undefined' && ( isNative(MutationObserver) || // PhantomJS and iOS 7.x MutationObserver.toString() === '[object MutationObserverConstructor]' )) {

timerFunc = () => { // ... } isUsingMicroTask = true } else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) { timerFunc = () => { setImmediate(flushCallbacks) } } else { // Fallback to setTimeout. timerFunc = () => { setTimeout(flushCallbacks, 0) } }

来看下`timerFunc`的取值逻辑:

  1、 我们知道异步任务有两种,其中 `microtask` 要优于 `macrotask` ,所以优先选择 `Promise` 。因此这里先判断浏览器是否支持 `Promise`。

  2、 如果不支持再考虑 `macrotask` 。对于 `macrotask` 会先后判断浏览器是否支持 `MutationObserver` 和 `setImmediate` 。

  3、 如果都不支持就只能使用 `setTimeout` 。这也从侧面展示出了 `macrotask` 中 `setTimeout` 的性能是最差的。

> `nextTick`中 `if (!pending)` 语句中 `pending` 作用显然是让 `if` 语句的逻辑只执行一次,而它其实就代表 `callbacks` 中是否有事件在等待执行。

这里的`flushCallbacks`函数的主要逻辑就是将 `pending` 置为 `false` 以及清空 `callbacks` 数组,然后遍历 `callbacks` 数组,执行里面的每一个函数。

* `nextTick`的最后一步对应:
```js
if (!cb && typeof Promise !== 'undefined') {
  return new Promise(resolve => {
    _resolve = resolve
  })
}

这里 if 对应的情况是我们调用 nextTick 函数时没有传入回调函数并且浏览器支持 Promise ,那么就会返回一个 Promise 实例,并且将 resolve 赋值给 _resolve。回到nextTick开头的一段代码:

let _resolve
callbacks.push(() => {
  if (cb) {
    try {
      cb.call(ctx)
    } catch (e) {
      handleError(e, ctx, 'nextTick')
    }
  } else if (_resolve) {
    _resolve(ctx)
  }
})

当我们执行 callbacks 的函数时,发现没有 cb 而有 _resolve 时就会执行之前返回的 Promise 对象的 resolve 函数。