Vue中之nextTick函數(shù)源碼分析詳解
1. 什么是Vue.nextTick()?
官方文檔解釋如下:
在下次DOM更新循環(huán)結(jié)束之后執(zhí)行的延遲回調(diào)。在修改數(shù)據(jù)之后立即使用這個(gè)方法,獲取更新后的DOM。
2. 為什么要使用nextTick?
<!DOCTYPE html>
<html>
<head>
<title>演示Vue</title>
<script src="https://tugenhua0707.github.io/vue/vue1/vue.js"></script>
</head>
<body>
<div id="app">
<template>
<div ref="list">
{{name}}
</div>
</template>
</div>
<script>
new Vue({
el: '#app',
data: {
name: 'aa'
},
mounted() {
this.updateData();
},
methods: {
updateData() {
var self = this;
this.name = 'bb';
console.log(this.$el.textContent); // aa
this.$nextTick(function(){
console.log(self.$el.textContent); // bb
});
}
}
});
</script>
</body>
</html>
如上代碼 在頁(yè)面視圖上顯示bb,但是當(dāng)我在控制臺(tái)打印的時(shí)候,獲取的文本內(nèi)容還是 aa,但是使用 nextTick后,獲取的文本內(nèi)容就是最新的內(nèi)容bb了,因此在這種情況下,我們可以使用nextTick函數(shù)了。
上面的代碼為什么改變this.name = 'bb';后,再使用console.log(this.$el.textContent);打印的值還是aa呢?那是因?yàn)樵O(shè)置name的值后,DOM還沒(méi)有更新到,所以獲取值還是之前的值,但是我們放到nextTick函數(shù)里面的時(shí)候,代碼會(huì)在DOM更新后執(zhí)行,因此DOM更新后,再去獲取元素的值就可以獲取到最新值了。
理解DOM更新:在VUE中,當(dāng)我們修改了data中的某一個(gè)值后,并不會(huì)立即反應(yīng)到該el中,vue將對(duì)更改的數(shù)據(jù)放到watcher的一個(gè)異步隊(duì)列中,只有在當(dāng)前任務(wù)空閑時(shí)才會(huì)執(zhí)行watcher隊(duì)列任務(wù),這就有一個(gè)延遲時(shí)間,因此放到 nextTick函數(shù)后就可以獲取該el的最新值了。如果我們把上面的nextTick改成setTimeout也是可以的。
3. Vue源碼詳解之nextTick(源碼在 vue/src/core/util/env.js)
在理解nextTick源碼之前,我們先來(lái)理解下 html5中新增的 MutationObserver的API,它的作用是用來(lái)監(jiān)聽(tīng)DOM變動(dòng)的接口,它能監(jiān)聽(tīng)一個(gè)dom對(duì)象發(fā)生的子節(jié)點(diǎn)刪除,屬性修改,文本內(nèi)容修改等等。
nextTick源碼如下:
export const nextTick = (function () {
const callbacks = []
let pending = false
let timerFunc
function nextTickHandler () {
pending = false;
/*
之所以要slice復(fù)制一份出來(lái)是因?yàn)橛械腸b執(zhí)行過(guò)程中又會(huì)往callbacks中加入內(nèi)容,比如$nextTick的回調(diào)函數(shù)里又有$nextTick,
那么這些應(yīng)該放入到下一個(gè)輪次的nextTick去執(zhí)行,所以拷貝一份,遍歷完成即可,防止一直循環(huán)下去。
*/
const copies = callbacks.slice(0)
callbacks.length = 0
for (let i = 0; i < copies.length; i++) {
copies[i]()
}
}
// the nextTick behavior leverages the microtask queue, which can be accessed
// via either native Promise.then or MutationObserver.
// MutationObserver has wider support, however it is seriously bugged in
// UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It
// completely stops working after triggering a few times... so, if native
// Promise is available, we will use it:
/* istanbul ignore if */
/*
nextTick行為利用了microtask隊(duì)列, 先使用 Promise.resolve().then(nextTickHandler)來(lái)將異步回調(diào)
放入到microtask中,Promise 和 MutationObserver都可以使用,但是 MutationObserver 在IOS9.3以上的
WebView中有bug,因此如果滿足第一項(xiàng)的話就可以執(zhí)行,如果沒(méi)有原生Promise就用 MutationObserver。
*/
if (typeof Promise !== 'undefined' && isNative(Promise)) {
var p = Promise.resolve()
var logError = err => { console.error(err) }
timerFunc = () => {
p.then(nextTickHandler).catch(logError)
// in problematic UIWebViews, Promise.then doesn't completely break, but
// it can get stuck in a weird state where callbacks are pushed into the
// microtask queue but the queue isn't being flushed, until the browser
// needs to do some other work, e.g. handle a timer. Therefore we can
// "force" the microtask queue to be flushed by adding an empty timer.
if (isIOS) setTimeout(noop)
}
} else if (typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
// PhantomJS and iOS 7.x
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
// use MutationObserver where native Promise is not available,
// e.g. PhantomJS IE11, iOS7, Android 4.4
/*
創(chuàng)建一個(gè)MutationObserver,observe監(jiān)聽(tīng)到DOM改動(dòng)之后執(zhí)行的回調(diào) nextTickHandler
*/
var counter = 1
var observer = new MutationObserver(nextTickHandler)
var textNode = document.createTextNode(String(counter));
// 使用MutationObserver的接口,監(jiān)聽(tīng)文本節(jié)點(diǎn)的字符內(nèi)容
observer.observe(textNode, {
characterData: true
});
/*
每次執(zhí)行timerFunc函數(shù)都會(huì)讓文本節(jié)點(diǎn)的內(nèi)容在0/1之間切換,切換之后將新賦值到那個(gè)我們MutationObserver監(jiān)聽(tīng)的文本節(jié)點(diǎn)上去。
*/
timerFunc = () => {
counter = (counter + 1) % 2
textNode.data = String(counter)
}
} else {
// fallback to setTimeout
/*
如果上面的兩種都不支持的話,我們就使用setTimeout來(lái)執(zhí)行
*/
timerFunc = () => {
setTimeout(nextTickHandler, 0)
}
}
return function queueNextTick (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)
}
});
/* 如果pending為true,表明本輪事件循環(huán)中已經(jīng)執(zhí)行過(guò) timerFunc(nextTickHandler, 0) */
if (!pending) {
pending = true
timerFunc()
}
if (!cb && typeof Promise !== 'undefined') {
return new Promise((resolve, reject) => {
_resolve = resolve
})
}
}
})()
整體思路理解:首先 nextTick 是一個(gè)閉包函數(shù),代碼立即執(zhí)行,在理解整體代碼之前,我們先來(lái)看個(gè)類似的demo,如下代碼:
<!DOCTYPE html>
<html>
<head>
<title>演示Vue</title>
</head>
<body>
<div id="app">
</div>
<script>
var nextTick = (function(){
return function queueNextTick(cb, ctx) {
if (cb) {
try {
cb.call(ctx)
} catch (e) {
console.log('出錯(cuò)了');
}
}
}
})();
// 方法調(diào)用
nextTick(function(){
console.log(2); // 打印2
})
</script>
</body>
</html>
demo代碼和上面的代碼很類似。
我們也可以再來(lái)抽離使用nextTick做demo代碼如下:
var nextTick2 = (function(){
const callbacks = [];
let pending = false;
let timerFunc;
function nextTickHandler () {
pending = false
const copies = callbacks.slice(0)
callbacks.length = 0
for (let i = 0; i < copies.length; i++) {
copies[i]()
}
}
if (typeof Promise !== 'undefined') {
var p = Promise.resolve()
var logError = err => { console.error(err) }
timerFunc = () => {
p.then(nextTickHandler).catch(logError)
}
} else if (typeof MutationObserver !== 'undefined' ||
// PhantomJS and iOS 7.x
MutationObserver.toString() === '[object MutationObserverConstructor]'
) {
// use MutationObserver where native Promise is not available,
// e.g. PhantomJS IE11, iOS7, Android 4.4
var counter = 1
var observer = new MutationObserver(nextTickHandler)
var textNode = document.createTextNode(String(counter))
observer.observe(textNode, {
characterData: true
})
timerFunc = () => {
counter = (counter + 1) % 2
textNode.data = String(counter)
}
} else {
// fallback to setTimeout
/* istanbul ignore next */
timerFunc = () => {
setTimeout(nextTickHandler, 0)
}
}
return function queueNextTick (cb, ctx) {
let _resolve
callbacks.push(() => {
if (cb) {
try {
cb.call(ctx)
} catch (e) {
handleError(e, ctx, 'nextTick')
}
} else if (_resolve) {
_resolve(ctx)
}
})
if (!pending) {
pending = true
timerFunc()
}
if (!cb && typeof Promise !== 'undefined') {
return new Promise((resolve, reject) => {
_resolve = resolve
})
}
}
})();
nextTick2(function(){
console.log(2222);
});
如上代碼是nextTick源碼的抽離,為了更好的理解nextTick,做了如上的demo。
我們?cè)賮?lái)理解一下整體的代碼的含義;
先定義數(shù)組 callbacks = [];來(lái)存放所有需要執(zhí)行的回調(diào)函數(shù),定義let pending = false;判斷本輪事件是否執(zhí)行過(guò) timerFunc(nextTickHandler, 0)這個(gè)函數(shù),為true說(shuō)明執(zhí)行過(guò) timeFunc函數(shù),接著定義nextTickHandler函數(shù),該函數(shù)的作用是依次遍歷數(shù)組callbacks保存的函數(shù),依次執(zhí)行;
請(qǐng)看源代碼如下:
function nextTickHandler () {
pending = false
const copies = callbacks.slice(0)
callbacks.length = 0
for (let i = 0; i < copies.length; i++) {
copies[i]()
}
}
然后就是三個(gè)判斷了,代碼如下:
if (typeof Promise !== 'undefined' && isNative(Promise)) {
var p = Promise.resolve();
var logError = err => { console.error(err) }
timerFunc = () => {
p.then(nextTickHandler).catch(logError);
} else if (typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
// PhantomJS and iOS 7.x
MutationObserver.toString() === '[object MutationObserverConstructor]'
)){
var counter = 1
var observer = new MutationObserver(nextTickHandler)
var textNode = document.createTextNode(String(counter))
observer.observe(textNode, {
characterData: true
})
timerFunc = () => {
counter = (counter + 1) % 2
textNode.data = String(counter)
}
} else {
timerFunc = () => {
setTimeout(nextTickHandler, 0)
}
}
首先判斷是否支持Promise對(duì)象,如果支持的話,定義了timeFunc()函數(shù),為了下一步調(diào)用做準(zhǔn)備,然后繼續(xù)判斷是否支持該對(duì)象 MutationObserver,如果支持的話,創(chuàng)建一個(gè)文本節(jié)點(diǎn),監(jiān)聽(tīng)該節(jié)點(diǎn)數(shù)據(jù)是否發(fā)生改變,如果發(fā)生改變的話,調(diào)用timerFunc函數(shù),counter值會(huì)在0/1切換,如果值改變了的話,把該數(shù)據(jù)值賦值到data屬性上面去,那么data屬性發(fā)生改變了,就會(huì)重新渲染頁(yè)面(因?yàn)関ue是通過(guò)Object.defineProperty來(lái)監(jiān)聽(tīng)屬性值是否發(fā)生改變),如果上面兩種情況都不滿足的話,那么直接使用setTimeout來(lái)執(zhí)行nextTickHandler函數(shù)了;
最后nextTick代碼返回一個(gè)函數(shù),代碼如下:
return function queueNextTick (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)
}
})
if (!pending) {
pending = true
timerFunc()
}
if (!cb && typeof Promise !== 'undefined') {
return new Promise((resolve, reject) => {
_resolve = resolve
})
}
}
代碼的含義是:傳入的cb是否是函數(shù),ctx參數(shù)是否是一個(gè)對(duì)象,如果cb是一個(gè)函數(shù)的話,使用cb.call(ctx), 如果timerFunc沒(méi)有執(zhí)行過(guò)的話,那么pending為false,因此執(zhí)行 timerFunc()函數(shù)?;镜乃悸肪褪沁@樣的。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
vue?elementUi中的tabs標(biāo)簽頁(yè)使用教程
Tabs 組件提供了選項(xiàng)卡功能,默認(rèn)選中第一個(gè)標(biāo)簽頁(yè),下面這篇文章主要給大家介紹了關(guān)于vue?elementUi中的tabs標(biāo)簽頁(yè)使用的相關(guān)資料,文中通過(guò)圖文介紹的非常詳細(xì),需要的朋友可以參考下2023-03-03
Vue3使用customRef封裝防抖函數(shù)的方法詳解
防抖函數(shù)的作用是高頻率觸發(fā)的事件,在指定的單位時(shí)間內(nèi),只響應(yīng)最后一次,如果在指定的時(shí)間內(nèi)再次觸發(fā),則重新計(jì)算時(shí)間,本文將給大家詳細(xì)的介紹一下Vue3使用customRef封裝防抖函數(shù)的方法,需要的朋友可以參考下2023-09-09
Vue監(jiān)聽(tīng)localstorage變化的方法詳解
在日常開(kāi)發(fā)中,我們經(jīng)常使用localStorage來(lái)存儲(chǔ)一些變量,這些變量會(huì)存儲(chǔ)在瀏覽中,對(duì)于localStorage來(lái)說(shuō),即使關(guān)閉瀏覽器,這些變量依然存儲(chǔ)著,方便我們開(kāi)發(fā)的時(shí)候在別的地方使用,本文就給大家介紹Vue如何監(jiān)聽(tīng)localstorage的變化,需要的朋友可以參考下2023-10-10
vue實(shí)現(xiàn)彈出框內(nèi)嵌頁(yè)面展示并添加tab切換展示實(shí)時(shí)加載
彈窗效果是在Web開(kāi)發(fā)中經(jīng)常用到的一種交互效果,這篇文章主要給大家介紹了關(guān)于vue實(shí)現(xiàn)彈出框內(nèi)嵌頁(yè)面展示并添加tab切換展示實(shí)時(shí)加載的相關(guān)資料,需要的朋友可以參考下2024-01-01
關(guān)閉eslint檢查和ts檢查的簡(jiǎn)單步驟記錄
這篇文章主要給大家介紹了關(guān)于關(guān)閉eslint檢查和ts檢查的相關(guān)資料,eslint是一個(gè)JavaScript的校驗(yàn)插件,通常用來(lái)校驗(yàn)語(yǔ)法或代碼的書(shū)寫風(fēng)格,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-02-02
基于vue v-for 循環(huán)復(fù)選框-默認(rèn)勾選第一個(gè)的實(shí)現(xiàn)方法
下面小編就為大家分享一篇基于vue v-for 循環(huán)復(fù)選框-默認(rèn)勾選第一個(gè)的實(shí)現(xiàn)方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-03-03
Vue手機(jī)號(hào)正則匹配姓名加密展示功能的實(shí)現(xiàn)
這篇文章主要介紹了Vue手機(jī)號(hào)正則匹配,姓名加密展示,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-08-08
Vue實(shí)現(xiàn)縱向的物流時(shí)間軸效果的示例代碼
在當(dāng)今數(shù)字化的時(shí)代,用戶體驗(yàn)的優(yōu)化至關(guān)重要,物流信息的展示作為電商和供應(yīng)鏈領(lǐng)域中的關(guān)鍵環(huán)節(jié),其呈現(xiàn)方式直接影響著用戶對(duì)貨物運(yùn)輸狀態(tài)的感知和滿意度,所以本文介紹了Vue實(shí)現(xiàn)縱向的物流時(shí)間軸效果的方法,需要的朋友可以參考下2024-08-08

