简化后的nextTick源码总该看懂了吧

73 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第3天。

用法

这个是官方的介绍用法:在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。

// 修改数据  
vm.msg = 'Hello'  
// DOM 还没有更新  
Vue.nextTick(function () {  
// DOM 更新了  
})  
  

Event Loop

怎么做到的呢?
首先我们要了解JavaScriptEvent Loop,简单来讲就是下面几个步骤:

  1. 所有同步任务都在主线程上执行,形成一个执行栈。
  2. 主线程外,存在一个任务队列。用来收集宏任务和微任务。
  3. 执行栈中的同步任务执行完毕,系统会读取任务队列,将所有的微任务执行掉,然后再去执行宏任务。
  4. 主线程不断重复上面的三步。

在浏览器环境,常见的宏任务有setTimeout,MessageChannel,postMessage,setImmediate;常见的微任务有MutationObsever和Promise.then。

也就是说,将nextTick函数中的callback函数添加到微任务或者宏任务中,在下一次宏任务之前,执行掉callback即可。

我们看看精简后的源码是怎么实现的。

源码解析

const callbacks = []
let pending = false

function flushCallbacks () {
  pending = false
  const copies = callbacks.slice(0)
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
    copies[i]()
  }
}

let timerFunc

if (typeof Promise !== 'undefined' && isNative(Promise)) {
  const p = Promise.resolve()
  timerFunc = () => {
    p.then(flushCallbacks)
  }
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
  timerFunc = () => {
    setImmediate(flushCallbacks)
  }
} else {
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}

export function nextTick (cb?: Function, ctx?: Object) {
  let _resolve
  callbacks.push(() => {
    if (cb) {
       cb.call(ctx)
    }
  })
  if (!pending) {
    pending = true
    timerFunc()
  }
}

三句话解释清楚nextTick源码做了那些事情:

  1. 根据浏览器环境设置timerFunc函数,利用promise或setImmediate或setTimeout,实现在下一个宏任务前执行fulshCallbacks函数
  2. nextTick函数用于收集cb函数,收集到callbacks数组;pending控制执行次数,在同一个宏任务中,只执行一次timerFuc函数
  3. flushCallbacks函数是将所有收集到的cb函数执行掉,并置空callbacks数组,将pending设置为false。