摘要:核心的異步延遲函數(shù),用于異步延遲調(diào)用函數(shù)優(yōu)先使用原生原本支持更廣,但在的中,觸摸事件處理程序中觸發(fā)會(huì)產(chǎn)生嚴(yán)重錯(cuò)誤的,回調(diào)被推入隊(duì)列但是隊(duì)列可能不會(huì)如期執(zhí)行。
淺析 Vue 2.6 中的 nextTick 方法。
事件循環(huán)JS 的 事件循環(huán) 和 任務(wù)隊(duì)列 其實(shí)是理解 nextTick 概念的關(guān)鍵。
這個(gè)網(wǎng)上其實(shí)有很多優(yōu)質(zhì)的文章做了詳細(xì)介紹,我就簡(jiǎn)單過(guò)過(guò)了。
以下內(nèi)容適用于瀏覽器端 JS,NodeJS 的事件循環(huán)機(jī)制并不相同。
規(guī)范中規(guī)定 task 分為兩大類(lèi): task(macrotask) 和 microtask。
通常認(rèn)為是 task 的任務(wù)源:
setTimeout / setInterval setImmediate MessageChannel I/O UI rendering
通常認(rèn)為是 microtask 的任務(wù)源:
Promise process.nextTick MutationObserver Object.observe(已廢棄)
簡(jiǎn)單概況:(這里是官方規(guī)范)
首先開(kāi)始執(zhí)行 script 腳本,直到執(zhí)行上下文棧為空時(shí),開(kāi)始清空 microtask 隊(duì)列里的任務(wù),隊(duì)列嘛,先入先出,出一個(gè)執(zhí)行一個(gè),清空完畢,走事件循環(huán)。
事件循環(huán):不斷地去取 task 隊(duì)列的中的一個(gè)任務(wù)推入棧中執(zhí)行,并在當(dāng)次循環(huán)里依次清空 microtask 隊(duì)列里的任務(wù),清空之后,可能會(huì)觸發(fā)頁(yè)面更新渲染(由瀏覽器決定)。
之后重復(fù) 事件循環(huán) 步驟。
nextTickVue 中數(shù)據(jù)的變化到 DOM 的更新渲染是一個(gè)異步過(guò)程。
此方法便用于在 DOM 更新循環(huán)結(jié)束之后執(zhí)行延遲回調(diào)。
使用方法很簡(jiǎn)單:
// 修改數(shù)據(jù) vm.msg = "Hello"; // DOM 還沒(méi)有更新 Vue.nextTick(function() { // DOM 更新了 }); // 作為一個(gè) Promise 使用 Vue.nextTick().then(function() { // DOM 更新了 });
源碼 去除注釋?zhuān)鋵?shí)也只有不到一百來(lái)行,整體還是很容易理解的。
這里劃成 3 個(gè)部分介紹。
模塊變量介紹 引入的模塊 和 定義的變量。
// noop 空函數(shù),可用作函數(shù)占位符 import { noop } from "shared/util"; // Vue 內(nèi)部的錯(cuò)誤處理函數(shù) import { handleError } from "./error"; // 判斷是IE/IOS/內(nèi)置函數(shù) import { isIE, isIOS, isNative } from "./env"; // 使用 MicroTask 的標(biāo)識(shí)符 export let isUsingMicroTask = false; // 以數(shù)組形式存儲(chǔ)執(zhí)行的函數(shù) const callbacks = []; // nextTick 執(zhí)行狀態(tài) let pending = false; // 遍歷函數(shù)數(shù)組執(zhí)行每一項(xiàng)函數(shù) function flushCallbacks() { pending = false; const copies = callbacks.slice(0); callbacks.length = 0; for (let i = 0; i < copies.length; i++) { copies[i](); } }異步延遲函數(shù)
接下來(lái)是核心的 異步延遲函數(shù)。這里不同的 Vue 版本采用的策略其實(shí)并不相同。
2.6 版本優(yōu)先使用 microtask 作為異步延遲包裝器。
2.5 版本則是 macrotask 結(jié)合 microtask。然而,在重繪之前狀態(tài)改變時(shí)會(huì)有小問(wèn)題(如 #6813)。此外,在事件處理程序中使用 macrotask 會(huì)導(dǎo)致一些無(wú)法規(guī)避的奇怪行為(如#7109,#7153,#7546,#7834,#8109)。
所以 2.6 版本現(xiàn)在又改用 microtask 了,為什么是又呢。。因?yàn)?2.4 版本及之前也是用的 microtask。。。
microtask 在某些情況下也是會(huì)有問(wèn)題的,因?yàn)?microtask 優(yōu)先級(jí)比較高,事件會(huì)在順序事件(如#4521,#6690 有變通方法)之間甚至在同一事件的冒泡過(guò)程中觸發(fā)(#6566)。
// 核心的異步延遲函數(shù),用于異步延遲調(diào)用 flushCallbacks 函數(shù) let timerFunc; // timerFunc 優(yōu)先使用原生 Promise // 原本 MutationObserver 支持更廣,但在 iOS >= 9.3.3 的 UIWebView 中,觸摸事件處理程序中觸發(fā)會(huì)產(chǎn)生嚴(yán)重錯(cuò)誤 if (typeof Promise !== "undefined" && isNative(Promise)) { const p = Promise.resolve(); timerFunc = () => { p.then(flushCallbacks); // IOS 的 UIWebView,Promise.then 回調(diào)被推入 microtask 隊(duì)列但是隊(duì)列可能不會(huì)如期執(zhí)行。 // 因此,添加一個(gè)空計(jì)時(shí)器“強(qiáng)制”執(zhí)行 microtask 隊(duì)列。 if (isIOS) setTimeout(noop); }; isUsingMicroTask = true; // 當(dāng)原生 Promise 不可用時(shí),timerFunc 使用原生 MutationObserver // 如 PhantomJS,iOS7,Android 4.4 // issue #6466 MutationObserver 在 IE11 并不可靠,所以這里排除了 IE } else if ( !isIE && typeof MutationObserver !== "undefined" && (isNative(MutationObserver) || // PhantomJS 和 iOS 7.x 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; // 如果原生 setImmediate 可用,timerFunc 使用原生 setImmediate } else if (typeof setImmediate !== "undefined" && isNative(setImmediate)) { timerFunc = () => { setImmediate(flushCallbacks); }; } else { // 最后的倔強(qiáng),timerFunc 使用 setTimeout timerFunc = () => { setTimeout(flushCallbacks, 0); }; }
一句話總結(jié)優(yōu)先級(jí):microtask 優(yōu)先。
Promise > MutationObserver > setImmediate > setTimeout
nextTick 函數(shù)。接受兩個(gè)參數(shù):
cb 回調(diào)函數(shù):是要延遲執(zhí)行的函數(shù);
ctx:指定 cb 回調(diào)函數(shù) 的 this 指向;
Vue 實(shí)例方法 $nextTick 做了進(jìn)一步封裝,把 ctx 設(shè)置為當(dāng)前 Vue 實(shí)例。
export function nextTick(cb?: Function, ctx?: Object) { let _resolve; // cb 回調(diào)函數(shù)會(huì)經(jīng)統(tǒng)一處理壓入 callbacks 數(shù)組 callbacks.push(() => { if (cb) { // 給 cb 回調(diào)函數(shù)執(zhí)行加上了 try-catch 錯(cuò)誤處理 try { cb.call(ctx); } catch (e) { handleError(e, ctx, "nextTick"); } } else if (_resolve) { _resolve(ctx); } }); // 執(zhí)行異步延遲函數(shù) timerFunc if (!pending) { pending = true; timerFunc(); } // 當(dāng) nextTick 沒(méi)有傳入函數(shù)參數(shù)的時(shí)候,返回一個(gè) Promise 化的調(diào)用 if (!cb && typeof Promise !== "undefined") { return new Promise(resolve => { _resolve = resolve; }); } }小結(jié)
整體看過(guò)來(lái),感覺(jué)還是比較好理解的吧~ 2.6 版本相比之前簡(jiǎn)化了一點(diǎn)。
小結(jié)一下,每次調(diào)用 Vue.nextTick(cb) 會(huì)做些什么:
cb 函數(shù)經(jīng)處理壓入 callbacks 數(shù)組,執(zhí)行 timerFunc 函數(shù),延遲調(diào)用 flushCallbacks 函數(shù),遍歷執(zhí)行 callbacks 數(shù)組中的所有函數(shù)。
延遲調(diào)用優(yōu)先級(jí)如下:
Promise > MutationObserver > setImmediate > setTimeout
其實(shí) Vue 2.4、2.5、2.6 版本的 nextTick 策略都略不一樣。
整體 2.6 和 2.4 的比較相似。(仔細(xì)瞅了瞅,基本就是一樣的,2.6 timerFunc 多了個(gè) setImmediate 判斷)
2.5 版本其實(shí)也差不多。。。源碼寫(xiě)法有些不一樣,整體優(yōu)先級(jí)是:Promise > setImmediate > MessageChannel > setTimeout,如果更新是在 v-on 事件處理程序中觸發(fā)的,nextTick 會(huì)優(yōu)先使用 macrotask。感興趣可自行去末尾的參考地址進(jìn)行查閱。
參考:
API—Vue.js
事件循環(huán)規(guī)范
Vue nextTick v2.6.7 源碼
Vue nextTick v2.5.22 源碼
Vue nextTick v2.4.4 源碼
從 event loop 規(guī)范探究 javaScript 異步及瀏覽器更新渲染時(shí)機(jī)
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/102206.html
摘要:淺析的特點(diǎn)之一就是響應(yīng)式,但數(shù)據(jù)更新時(shí),并不會(huì)立即更新。盡管已經(jīng)更新,但新增的元素并不立即插入到中。實(shí)際在中,執(zhí)行了,這也是自動(dòng)綁定到執(zhí)行上下文的原因。在內(nèi),使用數(shù)組保存回調(diào)函數(shù),表示當(dāng)前狀態(tài),使用函數(shù)來(lái)執(zhí)行回調(diào)隊(duì)列。 Vue.nextTick 淺析 Vue 的特點(diǎn)之一就是響應(yīng)式,但數(shù)據(jù)更新時(shí),DOM 并不會(huì)立即更新。當(dāng)我們有一個(gè)業(yè)務(wù)場(chǎng)景,需要在 DOM 更新之后再執(zhí)行一段代碼時(shí),可以...
摘要:盡量把所有異步代碼放在一個(gè)宏微任務(wù)中,減少消耗加快異步代碼的執(zhí)行。我們知道,如果一個(gè)異步代碼就注冊(cè)一個(gè)宏微任務(wù)的話,那么執(zhí)行完全部異步代碼肯定慢很多避免頻繁地更新。中就算我們一次性修改多次數(shù)據(jù),頁(yè)面還是只會(huì)更新一次。 寫(xiě)文章不容易,點(diǎn)個(gè)贊唄兄弟專(zhuān)注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧研究基于 Vue版本 【2.5...
摘要:這么講,有點(diǎn)籠統(tǒng),準(zhǔn)確地說(shuō),應(yīng)該是事件回調(diào)執(zhí)行過(guò)程中,在主線程為空之后,異步代碼執(zhí)行之前,所有通過(guò)注冊(cè)的異步代碼都是用宏任務(wù)。 寫(xiě)文章不容易,點(diǎn)個(gè)贊唄兄弟專(zhuān)注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧研究基于 Vue版本 【2.5.17】 如果你覺(jué)得排版難看,請(qǐng)點(diǎn)擊 下面鏈接 或者 拉到 下面關(guān)注公眾號(hào)也可以吧 【...
摘要:通常會(huì)做很多判斷來(lái)選擇存在的類(lèi)型,比如判斷等是否存在,而選擇他為微任務(wù)類(lèi)型但是可能宏微任務(wù)最后都是,因?yàn)樗潜J丶嫒萏幚怼? 寫(xiě)文章不容易,點(diǎn)個(gè)贊唄兄弟專(zhuān)注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧研究基于 Vue版本 【2.5.17】 如果你覺(jué)得排版難看,請(qǐng)點(diǎn)擊 下面鏈接 或者 拉到 下面關(guān)注公眾號(hào)也可以吧 【V...
摘要:哪吒別人的看法都是狗屁,你是誰(shuí)只有你自己說(shuō)了才算,這是爹教我的道理。哪吒去他個(gè)鳥(niǎo)命我命由我,不由天是魔是仙,我自己決定哪吒白白搭上一條人命,你傻不傻敖丙不傻誰(shuí)和你做朋友太乙真人人是否能夠改變命運(yùn),我不曉得。我只曉得,不認(rèn)命是哪吒的命。 showImg(https://segmentfault.com/img/bVbwiGL?w=900&h=378); 出處 查看github最新的Vue...
閱讀 1905·2021-11-17 09:33
閱讀 3624·2021-11-16 11:40
閱讀 3144·2019-08-30 11:23
閱讀 1128·2019-08-29 16:36
閱讀 2539·2019-08-29 13:23
閱讀 1844·2019-08-29 12:59
閱讀 1673·2019-08-29 12:42
閱讀 2055·2019-08-28 18:22