摘要:這是因?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è)api是怎么實(shí)現(xiàn)的呢?你肯定也有些疑問或者好奇。下面就是我的探索,分享給大家,也歡迎大家到github上和我進(jìn)行討論哈~~
首先貼一下vue的源碼,然后我們?cè)僖徊讲降姆治?/p>
/* @flow */ /* globals MessageChannel */ import { noop } from "shared/util" import { handleError } from "./error" import { isIOS, isNative } from "./env" 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 both microtasks and (macro) tasks. // In < 2.4 we used microtasks everywhere, but there are some scenarios where // microtasks have too high a priority and fire in between supposedly // sequential events (e.g. #4521, #6690) or even between bubbling of the same // event (#6566). However, using (macro) tasks everywhere also has subtle problems // when state is changed right before repaint (e.g. #6813, out-in transitions). // Here we use microtask by default, but expose a way to force (macro) task when // needed (e.g. in event handlers attached by v-on). let microTimerFunc let macroTimerFunc let useMacroTask = false // Determine (macro) task defer implementation. // Technically setImmediate should be the ideal choice, but it"s only available // in IE. The only polyfill that consistently queues the callback after all DOM // events triggered in the same loop is by using MessageChannel. /* istanbul ignore if */ if (typeof setImmediate !== "undefined" && isNative(setImmediate)) { macroTimerFunc = () => { setImmediate(flushCallbacks) } } else if (typeof MessageChannel !== "undefined" && ( isNative(MessageChannel) || // PhantomJS MessageChannel.toString() === "[object MessageChannelConstructor]" )) { const channel = new MessageChannel() const port = channel.port2 channel.port1.onmessage = flushCallbacks macroTimerFunc = () => { port.postMessage(1) } } else { /* istanbul ignore next */ macroTimerFunc = () => { setTimeout(flushCallbacks, 0) } } // Determine microtask defer implementation. /* istanbul ignore next, $flow-disable-line */ if (typeof Promise !== "undefined" && isNative(Promise)) { const p = Promise.resolve() microTimerFunc = () => { 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) } } else { // fallback to macro microTimerFunc = macroTimerFunc } /** * Wrap a function so that if any code inside triggers state change, * the changes are queued using a (macro) task instead of a microtask. */ export function withMacroTask (fn: Function): Function { return fn._withTask || (fn._withTask = function () { useMacroTask = true const res = fn.apply(null, arguments) useMacroTask = false return res }) } 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 if (useMacroTask) { macroTimerFunc() } else { microTimerFunc() } } // $flow-disable-line if (!cb && typeof Promise !== "undefined") { return new Promise(resolve => { _resolve = resolve }) } }
這么多代碼,可能猛的一看,可能有點(diǎn)懵,不要緊,我們一步一步抽出枝干。首先我們看一下這個(gè)js文件里的nextTick的定義
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 if (useMacroTask) { macroTimerFunc() } else { microTimerFunc() } } // $flow-disable-line if (!cb && typeof Promise !== "undefined") { return new Promise(resolve => { _resolve = resolve }) } }
我將代碼精簡一下。如下所示,下面的代碼估計(jì)就比較容易看懂了。把cb函數(shù)放到會(huì)掉隊(duì)列里去,如果支持macroTask,則利用macroTask在下一個(gè)事件循環(huán)中執(zhí)行這些異步的任務(wù),如果不支持macroTask,那就利用microTask在下一個(gè)事件循環(huán)中執(zhí)行這些異步任務(wù)。
export function nextTick (cb?: Function, ctx?: Object) { callbacks.push(cb) if (useMacroTask) { macroTimerFunc() } else { microTimerFunc() } }
這里再次普及一下js的event loop的相關(guān)知識(shí),js中的兩個(gè)任務(wù)隊(duì)列 :macrotasks、microtasks
macrotasks: script(一個(gè)js文件),setTimeout, setInterval, setImmediate, I/O, UI rendering
microtasks: process.nextTick, Promises, Object.observe, MutationObserver
執(zhí)行過程:
1.js引擎從macrotask隊(duì)列中取一個(gè)任務(wù)執(zhí)行
2.然后將microtask隊(duì)列中的所有任務(wù)依次執(zhí)行完
3.再次從macrotask隊(duì)列中取一個(gè)任務(wù)執(zhí)行
4.然后再次將microtask隊(duì)列中所有任務(wù)依次執(zhí)行完
……
循環(huán)往復(fù)
那么我們?cè)倏次覀兙喌舻拇a都是干什么的呢?我們往異步隊(duì)列里放回調(diào)函數(shù)的時(shí)候,我們并不是直接放回調(diào)函數(shù),而是包裝了一個(gè)函數(shù),在這個(gè)函數(shù)里調(diào)用cb,并且用try catch包裹了一下。這是因?yàn)閏b在運(yùn)行時(shí)出錯(cuò),我們不try catch這個(gè)錯(cuò)誤的話,會(huì)導(dǎo)致整個(gè)程序崩潰掉。 我們還精簡掉了如下代碼
if (!cb && typeof Promise !== "undefined") { return new Promise(resolve => { _resolve = resolve }) }
這段代碼是干嘛的呢?也就是說我們用nextTick的時(shí)候,還可以有promise的寫法。如果沒有向nextTick中傳入cb,并且瀏覽器支持Promise的話,我們的nextTick返回的將是一個(gè)Promise。所以,nextTick的寫法也可以是如下這樣的
nextTick().then(()=>{console.log("XXXXX")})
vue源碼里關(guān)于nextTick的封裝的思路,也給我們一些非常有益的啟示,就是我們平時(shí)在封裝函數(shù)的時(shí)候,要想同時(shí)指出回調(diào)和promise的話,就可以借鑒vue中的思路。
大致的思路我們已經(jīng)捋順了。但是為什么執(zhí)行macroTimerFunc或者microTimerFunc就會(huì)在下一個(gè)tick執(zhí)行我們的回調(diào)隊(duì)列呢?下面我們來分析一下這兩個(gè)函數(shù)的定義。首先我們分析macroTimerFunc
let macroTimerFunc if (typeof setImmediate !== "undefined" && isNative(setImmediate)) { macroTimerFunc = () => { setImmediate(flushCallbacks) } } else if (typeof MessageChannel !== "undefined" && ( isNative(MessageChannel) || // PhantomJS MessageChannel.toString() === "[object MessageChannelConstructor]" )) { const channel = new MessageChannel() const port = channel.port2 channel.port1.onmessage = flushCallbacks macroTimerFunc = () => { port.postMessage(1) } } else { /* istanbul ignore next */ macroTimerFunc = () => { setTimeout(flushCallbacks, 0) } }
從上邊的代碼可以看出,如果瀏覽器支持setImmediate,我們就用setImmediate,如果瀏覽器支持MessageChannel,我們就用MessageChannel的異步特性,如果兩者都不支持,我們就降價(jià)到setTimeout
,用setTimeout來把callbacks中的任務(wù)在下一個(gè)tick中執(zhí)行
macroTimerFunc = () => { setTimeout(flushCallbacks, 0) }
分析完macroTimerFunc,下面我們開始分析microTimerFunc,我把vue源碼中關(guān)于microTimerFunc的定義稍微精簡一下
let microTimerFunc; if (typeof Promise !== "undefined" && isNative(Promise)) { const p = Promise.resolve() microTimerFunc = () => { p.then(flushCallbacks) } } else { // fallback to macro microTimerFunc = macroTimerFunc }
從上邊精簡之后的代碼,我們可以看到microTimerFunc的實(shí)現(xiàn)思路。如果支持瀏覽器支持promise,就用promise實(shí)現(xiàn)。如果不支持,就降低到用macroTimerFunc
over,整體邏輯就是這樣。。看著嚇人,掰開了之后好好分析一下,還是挺簡單的。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/96240.html
摘要:因?yàn)槠綍r(shí)使用都是傳回調(diào)的,所以很好奇什么情況下會(huì)為,去翻看官方文檔發(fā)現(xiàn)起新增如果沒有提供回調(diào)且在支持的環(huán)境中,則返回一個(gè)。這就對(duì)了,函數(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...
摘要:復(fù)制代碼然后在這個(gè)文件里還有一個(gè)函數(shù)叫用來把保存的回調(diào)函數(shù)給全執(zhí)行并清空。其實(shí)調(diào)用的不僅是開發(fā)者,更新時(shí),也用到了。但是問題又來了,根據(jù)瀏覽器的渲染機(jī)制,渲染線程是在微任務(wù)執(zhí)行完成之后運(yùn)行的。 當(dāng)在代碼中更新了數(shù)據(jù),并希望等到對(duì)應(yīng)的Dom更新之后,再執(zhí)行一些邏輯。這時(shí),我們就會(huì)用到$nextTickfuncion call...
摘要:后來尤雨溪了解到是將回調(diào)放入的隊(duì)列。而且瀏覽器內(nèi)部為了更快的響應(yīng)用戶,內(nèi)部可能是有多個(gè)的而的的優(yōu)先級(jí)可能更高,因此對(duì)于尤雨溪采用的,甚至可能已經(jīng)多次執(zhí)行了的,都沒有執(zhí)行的,也就導(dǎo)致了我們更新操 原發(fā)于我的博客。 前一篇文章已經(jīng)詳細(xì)記述了Vue的核心執(zhí)行過程。相當(dāng)于已經(jīng)搞定了主線劇情。后續(xù)的文章都會(huì)對(duì)其中沒有介紹的細(xì)節(jié)進(jìn)行展開。 現(xiàn)在我們就來講講其他支線任務(wù):nextTick和micro...
摘要:原文地址項(xiàng)目地址關(guān)于中使用效果,官網(wǎng)上的解釋如下當(dāng)元素插入到樹或者從樹中移除的時(shí)候,屬性提供變換的效果,可以使用來定義變化效果,也可以使用來定義首先第一個(gè)函數(shù)是將元素插入,函數(shù)實(shí)現(xiàn)調(diào)用了實(shí)現(xiàn)代碼如下寫的好的代碼就是文檔,從注釋和命名上就 src/transition 原文地址項(xiàng)目地址 關(guān)于 vue 中使用 transition 效果,官網(wǎng)上的解釋如下: With Vue.js’ tra...
摘要:盡量把所有異步代碼放在一個(gè)宏微任務(wù)中,減少消耗加快異步代碼的執(zhí)行。我們知道,如果一個(gè)異步代碼就注冊(cè)一個(gè)宏微任務(wù)的話,那么執(zhí)行完全部異步代碼肯定慢很多避免頻繁地更新。中就算我們一次性修改多次數(shù)據(jù),頁面還是只會(huì)更新一次。 寫文章不容易,點(diǎn)個(gè)贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧研究基于 Vue版本 【2.5...
閱讀 2676·2021-11-16 11:53
閱讀 2737·2021-07-26 23:38
閱讀 2073·2019-08-30 15:55
閱讀 1751·2019-08-30 13:21
閱讀 3650·2019-08-29 17:26
閱讀 3306·2019-08-29 13:20
閱讀 875·2019-08-29 12:20
閱讀 3192·2019-08-26 10:21