摘要:而中的回調(diào)函數(shù)則會(huì)在頁面渲染后才執(zhí)行。還使用方法復(fù)制數(shù)組并把數(shù)組清空,這里的數(shù)組就是存放主線程執(zhí)行過程中的函數(shù)所傳的回調(diào)函數(shù)集合主線程可能會(huì)多次使用方法。到這里就已經(jīng)實(shí)現(xiàn)了根據(jù)環(huán)境選擇異步方法,并在異步方法中依次調(diào)用傳入方法的回調(diào)函數(shù)。
Vue.nextTick的應(yīng)用場景
Vue 是采用異步的方式執(zhí)行 DOM 更新。只要觀察到數(shù)據(jù)變化,Vue 將開啟一個(gè)隊(duì)列,并緩沖同一事件循環(huán)中發(fā)生的所有數(shù)據(jù)改變。然后,在下一個(gè)的事件循環(huán)中,Vue 刷新隊(duì)列并執(zhí)行頁面渲染工作。所以修改數(shù)據(jù)后DOM并不會(huì)立刻被重新渲染,如果想在數(shù)據(jù)更新后對頁面執(zhí)行DOM操作,可以在數(shù)據(jù)變化之后立即使用 Vue.nextTick(callback)。
下面這一段摘自vue官方文檔,關(guān)于JS 運(yùn)行機(jī)制的說明:
JS 執(zhí)行是單線程的,它是基于事件循環(huán)的。事件循環(huán)大致分為以下幾個(gè)步驟:
(1)所有同步任務(wù)都在主線程上執(zhí)行,形成一個(gè)執(zhí)行棧(execution context stack)。
(2)主線程之外,還存在一個(gè)"任務(wù)隊(duì)列"(task queue)。只要異步任務(wù)有了運(yùn)行結(jié)果,就在"任務(wù)隊(duì)列"之中放置一個(gè)事件。
(3)一旦"執(zhí)行棧"中的所有同步任務(wù)執(zhí)行完畢,系統(tǒng)就會(huì)讀取"任務(wù)隊(duì)列",看看里面有哪些事件。那些對應(yīng)的異步任務(wù),于是結(jié)束等待狀態(tài),進(jìn)入執(zhí)行棧,開始執(zhí)行。
(4)主線程不斷重復(fù)上面的第三步。
在主線程中執(zhí)行修改數(shù)據(jù)這一同步任務(wù),DOM的渲染事件就會(huì)被放到“任務(wù)隊(duì)列”中,當(dāng)“執(zhí)行?!敝型饺蝿?wù)執(zhí)行完畢,本次事件循環(huán)結(jié)束,系統(tǒng)才會(huì)讀取并執(zhí)行“任務(wù)隊(duì)列”中的頁面渲染事件。而Vue.nextTick中的回調(diào)函數(shù)則會(huì)在頁面渲染后才執(zhí)行。
例子如下:
{{test}}
// Some code... data () { return { test: "begin" }; }, // Some code... this.test = "end"; console.log(this.$refs.test.innerText);//"begin" this.nextTick(() => { console.log(this.$refs.test.innerText) //"end" })Vue.nextTick實(shí)現(xiàn)原理
Vue.nextTick的源碼vue項(xiàng)目/src/core/util/路徑下的next-tick.js文件,文章最后也會(huì)貼出完整的源碼
我們先來看看對于異步調(diào)用函數(shù)的實(shí)現(xiàn)方法:
let timerFunc if (typeof Promise !== "undefined" && isNative(Promise)) { const p = Promise.resolve() timerFunc = () => { p.then(flushCallbacks) if (isIOS) setTimeout(noop) } isUsingMicroTask = true } else if (!isIE && typeof MutationObserver !== "undefined" && ( isNative(MutationObserver) || MutationObserver.toString() === "[object MutationObserverConstructor]" )) { 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)) { timerFunc = () => { setImmediate(flushCallbacks) } } else { timerFunc = () => { setTimeout(flushCallbacks, 0) } }
這段代碼首先的檢測運(yùn)行環(huán)境的支持情況,使用不同的異步方法。優(yōu)先級(jí)依次是Promise、MutationObserver、setImmediate和setTimeout。這是根據(jù)運(yùn)行效率來做優(yōu)先級(jí)處理,有興趣可以去了解一下這幾種方法的差異??偟膩碚f,Event Loop分為宏任務(wù)以及微任務(wù),宏任務(wù)耗費(fèi)的時(shí)間是大于微任務(wù)的,所以優(yōu)先使用微任務(wù)。例如Promise屬于微任務(wù),而setTimeout就屬于宏任務(wù)。最終timerFunc則是我們調(diào)用nextTick函數(shù)時(shí)要內(nèi)部會(huì)調(diào)用的主要方法,那么flushCallbacks又是什么呢,我們在看看flushCallbacks函數(shù):
function flushCallbacks () { pending = false const copies = callbacks.slice(0) callbacks.length = 0 for (let i = 0; i < copies.length; i++) { copies[i]() } }
這個(gè)函數(shù)比較簡單,就是依次調(diào)用callbacks數(shù)組里面的方法。還使用slice()方法復(fù)制callbacks數(shù)組并把callbacks數(shù)組清空,這里的callbacks數(shù)組就是存放主線程執(zhí)行過程中的Vue.nextTick()函數(shù)所傳的回調(diào)函數(shù)集合(主線程可能會(huì)多次使用Vue.nextTick()方法)。到這里就已經(jīng)實(shí)現(xiàn)了根據(jù)環(huán)境選擇異步方法,并在異步方法中依次調(diào)用傳入Vue.nextTick()方法的回調(diào)函數(shù)。nextTick函數(shù)主要就是要將callback函數(shù)存在數(shù)組callbacks中,并調(diào)用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) } }) if (!pending) { pending = true timerFunc() } // $flow-disable-line if (!cb && typeof Promise !== "undefined") { return new Promise(resolve => { _resolve = resolve }) } }
可以看到可以傳入第二個(gè)參數(shù)作為回調(diào)函數(shù)的this。另外如果參數(shù)一的條件判斷為false時(shí)則返回一個(gè)Promise對象。例如
Vue.nextTick(null, {value: "test"}) .then((data) => { console.log(data.value) // "test" })
這里還使用了一個(gè)優(yōu)化技巧,用pending來標(biāo)記異步任務(wù)是否被調(diào)用,也就是說在同一個(gè)tick內(nèi)只調(diào)用一次timerFunc函數(shù),這樣就不會(huì)開啟多個(gè)異步任務(wù)。
完整的源碼:
import { noop } from "shared/util" import { handleError } from "./error" import { isIE, isIOS, isNative } from "./env" export let isUsingMicroTask = false const callbacks = [] 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]() } } // Here we have async deferring wrappers using microtasks. // In 2.5 we used (macro) tasks (in combination with microtasks). // However, it has subtle problems when state is changed right before repaint // (e.g. #6813, out-in transitions). // Also, using (macro) tasks in event handler would cause some weird behaviors // that cannot be circumvented (e.g. #7109, #7153, #7546, #7834, #8109). // So we now use microtasks everywhere, again. // A major drawback of this tradeoff is that there are some scenarios // where microtasks have too high a priority and fire in between supposedly // sequential events (e.g. #4521, #6690, which have workarounds) // or even between bubbling of the same event (#6566). let timerFunc // 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 next, $flow-disable-line */ if (typeof Promise !== "undefined" && isNative(Promise)) { const p = Promise.resolve() timerFunc = () => { p.then(flushCallbacks) // 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) } isUsingMicroTask = true } else if (!isIE && 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, iOS7, Android 4.4 // (#6466 MutationObserver is unreliable in IE11) 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)) { // Fallback to setImmediate. // Techinically it leverages the (macro) task queue, // but it is still a better choice than setTimeout. timerFunc = () => { setImmediate(flushCallbacks) } } else { // Fallback to setTimeout. timerFunc = () => { setTimeout(flushCallbacks, 0) } } 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) } }) if (!pending) { pending = true timerFunc() } // $flow-disable-line if (!cb && typeof Promise !== "undefined") { return new Promise(resolve => { _resolve = resolve }) } }
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/110256.html
摘要:因?yàn)槠綍r(shí)使用都是傳回調(diào)的,所以很好奇什么情況下會(huì)為,去翻看官方文檔發(fā)現(xiàn)起新增如果沒有提供回調(diào)且在支持的環(huán)境中,則返回一個(gè)。這就對了,函數(shù)體內(nèi)最后的判斷很明顯就是這個(gè)意思沒有回調(diào)支持。 Firstly, this paper is based on Vue 2.6.8剛開始接觸Vue的時(shí)候,哇nextTick好強(qiáng),咋就在這里面寫就是dom更新之后,當(dāng)時(shí)連什么macrotask、micro...
摘要:這是因?yàn)樵谶\(yùn)行時(shí)出錯(cuò),我們不這個(gè)錯(cuò)誤的話,會(huì)導(dǎo)致整個(gè)程序崩潰掉。如果沒有向中傳入,并且瀏覽器支持的話,我們的返回的將是一個(gè)。如果不支持,就降低到用,整體邏輯就是這樣。。 我們知道vue中有一個(gè)api。Vue.nextTick( [callback, context] )他的作用是在下次 DOM 更新循環(huán)結(jié)束之后執(zhí)行延遲回調(diào)。在修改數(shù)據(jù)之后立即使用這個(gè)方法,獲取更新后的 DOM。那么這個(gè)...
摘要:盡量把所有異步代碼放在一個(gè)宏微任務(wù)中,減少消耗加快異步代碼的執(zhí)行。我們知道,如果一個(gè)異步代碼就注冊一個(gè)宏微任務(wù)的話,那么執(zhí)行完全部異步代碼肯定慢很多避免頻繁地更新。中就算我們一次性修改多次數(shù)據(jù),頁面還是只會(huì)更新一次。 寫文章不容易,點(diǎn)個(gè)贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧研究基于 Vue版本 【2.5...
摘要:后來尤雨溪了解到是將回調(diào)放入的隊(duì)列。而且瀏覽器內(nèi)部為了更快的響應(yīng)用戶,內(nèi)部可能是有多個(gè)的而的的優(yōu)先級(jí)可能更高,因此對于尤雨溪采用的,甚至可能已經(jīng)多次執(zhí)行了的,都沒有執(zhí)行的,也就導(dǎo)致了我們更新操 原發(fā)于我的博客。 前一篇文章已經(jīng)詳細(xì)記述了Vue的核心執(zhí)行過程。相當(dāng)于已經(jīng)搞定了主線劇情。后續(xù)的文章都會(huì)對其中沒有介紹的細(xì)節(jié)進(jìn)行展開。 現(xiàn)在我們就來講講其他支線任務(wù):nextTick和micro...
摘要:中引入了中的中引入了中的中,定義了的構(gòu)造函數(shù)中的原型上掛載了方法,用來做初始化原型上掛載的屬性描述符,返回原型上掛載的屬性描述符返回原型上掛載與方法,用來為對象新增刪除響應(yīng)式屬性原型上掛載方法原型上掛載事件相關(guān)的方法。 入口尋找 入口platforms/web/entry-runtime-with-compiler中import了./runtime/index導(dǎo)出的vue。 ./r...
閱讀 1171·2021-11-24 09:38
閱讀 3613·2021-11-22 15:32
閱讀 3468·2019-08-30 15:54
閱讀 2575·2019-08-30 15:53
閱讀 1507·2019-08-30 15:52
閱讀 2563·2019-08-30 13:15
閱讀 1848·2019-08-29 12:21
閱讀 1408·2019-08-26 18:36