男人一事無成時的溫柔是最廉價的segmentfault
在理解nextTick以前,咱們先要了解js的運行機制瀏覽器
js執行是單線程的,基於事件循環,事件循環大體分爲如下幾個步驟:bash
(1)全部同步任務都在主線程上執行,造成一個執行棧(execution context stack)。異步
(2)主線程以外,還存在一個"任務隊列"(task queue)。只要異步任務有了運行結果,就在"任務隊列"之中放置一個事件,能夠看出,這個任務隊列主要存放異步任務的函數
(3)一旦"執行棧"中的全部同步任務執行完畢,系統就會讀取"任務隊列",看看裏面有哪些事件。那些對應的異步任務,因而結束等待狀態,進入執行棧,開始執行。ui
(4)主線程不斷重複上面的第三步。this
主線程的執行過程就是一個 tick,而全部的異步結果都是經過 「任務隊列」 來調度。 消息隊列中存放的是一個個的任務(task)。 規範中規定 task 分爲兩大類,分別是 macro task 和 micro task,而且每一個 macro task 執行結束後,都要檢查一遍micro task,並執行清理完成再進入下一輪事件循環。它的源碼很簡單,
源碼:src/core/util/next-tick.js線程
export let isUsingMicroTask = false
/* 存放異步執行的回調 */
const callbacks = []
/* 標記位,若是已經有timerFunc被推送到任務隊列中去則不須要重複推送 */
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]()
}
}
/* 一個函數指針,指向函數將被推送到任務隊列中,等到主線程任務執行完時,任務隊列中的timeFunc將被調用 */
let timerFunc
/* 兼容Promise的瀏覽器 */
if (typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve()
timerFunc = () => {
p.then(flushCallbacks)
}
/* 執行微任務標誌位 */
isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
// PhantomJS and iOS 7.x
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
/* 不支持Promise則使用MutationObserver */
let counter = 1
const observer = new MutationObserver(flushCallbacks)
const textNode = document.createTextNode(String(counter))
observer.observe(textNode, {
characterData: true
})
timerFunc = () => {
counter = (counter + 1) % 2
textNode.data = String(counter)
}
isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
/* 不支持Promise和MutationObserver則使用setImmediate */
timerFunc = () => {
setImmediate(flushCallbacks)
}
} else {
/* 不然都使用setTimeout */
timerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
/* 延遲一個任務使其異步執行,在下一個tick時執行,這個函數的做用是在task或者microtask中推入一個timerFunc,在當前調用棧執行完之後以此執行直到執行到timerFunc,目的是延遲到當前調用棧執行完之後執行 */
export function nextTick (cb?: Function, ctx?: Object) {
let _resolve
callbacks.push(() => {
if (cb) {
try {
cb.call(ctx)
} catch (e) {
handleError(e, ctx, 'nextTick')
}
} else if (_resolve) {
_resolve(ctx)
}
})
/* 已經有timerFunc被推送到任務隊列中去則不須要重複推送 */
if (!pending) {
pending = true
timerFunc()
}
/* nextTick沒有傳回調函數時,返回一個Promise,咱們能夠nextTick().then(() => {}) */
if (!cb && typeof Promise !== 'undefined') {
return new Promise(resolve => {
_resolve = resolve
})
}
}
複製代碼
解析:能夠看出,nextTick在內部對環境作了兼容,首先會檢測是否支持微任務:Promise,支持的話則把咱們傳入的回調押入隊列中在下一個tick中等待執行(flushCallbacks依次執行回調),若不支持則使用MutationObserver,如以上二者都不支持則使用宏任務:setImmediate和setTimeout。
最後若是咱們沒有給nextTick傳入回調,則能夠nextTick().then(() => {})跳到then邏輯中。指針
結合上一節的 setter 分析,咱們瞭解到數據的變化到 DOM 的從新渲染是一個異步過程,發生在下一個 tick。這就是咱們平時在開發的過程當中,好比從服務端接口去獲取數據的時候,數據作了修改,若是咱們的某些方法去依賴了數據修改後的 DOM 變化,咱們就必須在 nextTick 後執行。好比下面的僞代碼:
getData(res).then(()=>{
this.xxx = res.data
this.$nextTick(() => {
// 這裏咱們能夠獲取變化後的 DOM
})
})
複製代碼