前言:
本文需要一定的事件迴圈相關知識,想了解事件迴圈的小夥伴可以看這裡。
本文要弄明白下面兩件事:
$nextTick什麼時候執行
vue中nextTick與$nextTick區別
1。檢視原始碼中的$nextTick方法
Vue。prototype。$nextTick = function(fn) {
return nextTick(fn, this)
};
可以看到$nextTick呼叫的也是nextTick方法,只不過$nextTick預設綁定了this上下文,也就是Vue例項物件
2。下面檢視nextTick方法
function nextTick(cb, ctx) {
var _resolve;
callbacks。push(function() {
if (cb) {
try {
cb。call(ctx);
} catch (e) {
handleError(e, ctx, ‘nextTick’);
}
} elseif (_resolve) {
_resolve(ctx);
}
});
if (!pending) {
pending = true;
timerFunc();
}
// $flow-disable-line
if (!cb && typeof Promise !== ‘undefined’) {
return new Promise(function(resolve) {
_resolve = resolve;
})
}
}
callbacks一個非同步佇列,傳入的回撥函式會被儲存在這個陣列內,等待時機執行
if (!cb && typeof Promise !== ‘undefined’)如果沒有回撥方法,並且當前環境支援Promise,那麼nextTick返回的是一個Promise物件
3。檢視timerFunc方法
if (typeof Promise !== ‘undefined’ && isNative(Promise)) {
var p = Promise。resolve();
timerFunc = function() {
p。then(flushCallbacks);
//ios的UIWebViews中,回撥推送到微任務佇列後不會立即重新整理,透過新增空定時器來強制重新整理微任務佇列
if (isIOS) {
setTimeout(noop);
}
};
isUsingMicroTask = true;
} elseif (!isIE && typeof MutationObserver !== ‘undefined’ && (
isNative(MutationObserver) ||
// PhantomJS and iOS 7。x
MutationObserver。toString() === ‘[object MutationObserverConstructor]’
)) {
//如果支援MutationObserver
var counter = 1;
var observer = new MutationObserver(flushCallbacks);
var textNode = document。createTextNode(String(counter));
observer。observe(textNode, {
characterData: true
});
timerFunc = function() {
counter = (counter + 1) % 2;
textNode。data = String(counter);
};
isUsingMicroTask = true;
} elseif (typeof setImmediate !== ‘undefined’ && isNative(setImmediate)) {
// but it is still a better choice than setTimeout。
timerFunc = function() {
setImmediate(flushCallbacks);
};
} else {
// Fallback to setTimeout。
timerFunc = function() {
setTimeout(flushCallbacks, 0);
};
}
經過一系列的判斷方法,用來判斷當前執行環境到底支援哪種方法,可以看到最後timerFunc執行的都是flushCallbacks方法。
4。flushCallbacks方法
functionflushCallbacks() {
pending = false;
var copies = callbacks。slice(0);
callbacks。length = 0;
for (var i = 0; i < copies。length; i++) {
copies[i]();
}
}
這個方法執行時,會將回調佇列做一個淺複製,並且初始化這個佇列,防止影響下次事件迴圈,接下來將淺複製後的陣列進行迴圈並執行。
到這步,$nextTick方法就算執行完畢了。
總結:
nextTick與$nextTick方法基本一致,$nextTick方法預設繫結vue例項為上下文。
nextTick上下文需要傳入,若不傳入則預設繫結為window。
$nextTick方法執行時會判斷當前執行環境是否支援Promise若支援則放入Promise。then()內,若不支援則判斷是否支援MutationObserver,如果不支援的話則會判斷是否支援setImmediate方法,否則的話會加入setTimeout中。
一次事件迴圈(event loop)的過程
宏任務 => 所有微任務 => ui渲染
其中Promise。then以及MutationObserver為微任務,在當前事件迴圈執行。
setImmediate、setTimeout為宏任務,在下次事件迴圈執行。
下面程式碼為vue原始碼中的nextTick相關程式碼,我做了部分註釋。
var isUsingMicroTask = false; //是否使用MutationObserver來觸發回撥函式執行,另作他用,可以在原始碼中搜索用到的地方,本文不做深究
var callbacks = []; //儲存回撥函式
var pending = false; //此次nextTick是否執行中的標記
var timerFunc; //觸發方法
function noop(a, b, c) {} //空函式,ios用來強制重新整理微任務佇列
//執行回撥佇列
functionflushCallbacks() {
pending = false; //執行中標記置否
var copies = callbacks。slice(0); //淺複製回撥
callbacks。length = 0; // 清空陣列
for (var i = 0; i < copies。length; i++) {
copies[i](); //執行儲存的函式
}
}
//判斷當前環境是否支援Promise
//Vue 在內部對非同步佇列嘗試使用原生的 Promise。then、MutationObserver 和 setImmediate,
//如果執行環境不支援,則會採用 setTimeout(fn, 0) 代替。
if (typeof Promise !== ‘undefined’ && isNative(Promise)) {
var p = Promise。resolve();
timerFunc = function() {
p。then(flushCallbacks); //將flushCallbacks放入微任務
//ios的UIWebViews中,回撥推送到微任務佇列後不會立即重新整理,透過新增空定時器來強制重新整理微任務佇列
if (isIOS) {
setTimeout(noop);
}
};
isUsingMicroTask = true;
} elseif (!isIE && typeof MutationObserver !== ‘undefined’ && (
isNative(MutationObserver) ||
// PhantomJS and iOS 7。x
MutationObserver。toString() === ‘[object MutationObserverConstructor]’
)) {
//如果支援MutationObserver
var counter = 1;
var observer = new MutationObserver(flushCallbacks);
var textNode = document。createTextNode(String(counter));
observer。observe(textNode, {
characterData: true
});
timerFunc = function() {
counter = (counter + 1) % 2;
textNode。data = String(counter);
};
isUsingMicroTask = true;
} elseif (typeof setImmediate !== ‘undefined’ && isNative(setImmediate)) {
// but it is still a better choice than setTimeout。
timerFunc = function() {
setImmediate(flushCallbacks);
};
} else {
// Fallback to setTimeout。
timerFunc = function() {
setTimeout(flushCallbacks, 0);
};
}
function nextTick(cb, ctx) {
var _resolve;
callbacks。push(function() { //將函式加入回撥陣列中,函式執行時會自動執行傳入的回撥函式
if (cb) {
try {
cb。call(ctx); //更改this為傳入的ctx,並執行,$nextTick為vue。nextTick為傳入的上下文,如果沒傳則this為window
} catch (e) {
handleError(e, ctx, ‘nextTick’);
}
} elseif (_resolve) {
_resolve(ctx);
}
});
if (!pending) {
pending = true;
timerFunc(); //執行函式
}
// $flow-disable-line
if (!cb && typeof Promise !== ‘undefined’) {
return new Promise(function(resolve) {
_resolve = resolve;
})
}
}
Vue。prototype。$nextTick = function(fn) {
return nextTick(fn, this)
};