高階前端進階,vue如何實現$nextTick

高階前端進階,vue如何實現$nextTick

前言:

本文需要一定的事件迴圈相關知識,想了解事件迴圈的小夥伴可以看這裡。

本文要弄明白下面兩件事:

$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)

};