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