原始碼解讀-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');
}
} else if (_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;
} else if (!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;
} else if (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方法
function flushCallbacks() {
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用來強制重新整理微任務佇列
//執行回撥佇列
function flushCallbacks() {
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;
} else if (!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;
} else if (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');
}
} else if (_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)
};
如果想獲取更多內容,可以掃描下方二維碼,一起學習,一起進步。
相關文章
- Vue原始碼閱讀一:說說vue.nextTick實現Vue原始碼
- 鴻蒙OS的系統呼叫是如何實現的? | 解讀鴻蒙原始碼鴻蒙原始碼
- Vue2.0原始碼閱讀筆記(四):nextTickVue原始碼筆記
- vue的nextTick的實現Vue
- 從原始碼裡面瞭解vue的nextTick的使用原始碼Vue
- Vue原始碼解析之nextTickVue原始碼
- [精讀原始碼系列]Vue中DOM的非同步更新和Vue.nextTick()原始碼Vue非同步
- 基於原始碼分析Vue的nextTick原始碼Vue
- Vue原始碼閱讀- 批量非同步更新與nextTick原理Vue原始碼非同步
- Vue原始碼閱讀 - 批量非同步更新與nextTick原理Vue原始碼非同步
- 解讀vue-server-renderer原始碼並在react中的實現VueServer原始碼React
- 【React原始碼解讀】- 元件的實現React原始碼元件
- Vue原始碼解讀一Vue原始碼
- Vue原始碼解析,keep-alive是如何實現快取的?Vue原始碼Keep-Alive快取
- Axios 原始碼解讀 —— 原始碼實現篇iOS原始碼
- Maven 原始碼解析:依賴調解是如何實現的?Maven原始碼
- Vue 原始碼解讀(12)—— patchVue原始碼
- Vue 原始碼解讀(1)—— 前言Vue原始碼
- 原始碼解讀之FutureTask如何實現最大等待時間原始碼
- 「讀懂原始碼系列3」lodash 是如何實現深拷貝的(上)原始碼
- 從原始碼解讀Category實現原理原始碼Go
- PostgreSQL 原始碼解讀(218)- spinlock的實現SQL原始碼
- underscore 原始碼解讀之 bind 方法的實現原始碼
- PostgreSQL 原始碼解讀(3)- 如何閱讀原始碼SQL原始碼
- Vue中之nextTick函式原始碼分析Vue函式原始碼
- Vue 原始碼解讀(11)—— render helperVue原始碼
- Vue 原始碼解讀(7)—— Hook EventVue原始碼Hook
- Vue-Socket.io原始碼解讀Vue原始碼
- Vue2原始碼解讀(4) - 響應式原理及簡單實現Vue原始碼
- 閱讀vue原始碼後,簡單實現虛擬domVue原始碼
- 從 Vue3 原始碼中再談 nextTickVue原始碼
- 從Vue.js原始碼看nextTick機制Vue.js原始碼
- vue原始碼解讀-建構函式Vue原始碼函式
- Vue 原始碼解讀(4)—— 非同步更新Vue原始碼非同步
- Vue 原始碼解讀(5)—— 全域性 APIVue原始碼API
- Vue 原始碼解讀(6)—— 例項方法Vue原始碼
- vue原始碼中computed和watch的解讀Vue原始碼
- Vue.js 原始碼實現Vue.js原始碼