摘要:本篇文章主要是對(duì)中的異步更新策略和機(jī)制的解析,需要讀者有一定的使用經(jīng)驗(yàn)并且熟悉掌握事件循環(huán)模型。這個(gè)結(jié)果足以說(shuō)明中的更新并非同步。二是把回調(diào)函數(shù)放入一個(gè)隊(duì)列,等待適當(dāng)?shù)臅r(shí)機(jī)執(zhí)行。通過(guò)的主動(dòng)來(lái)觸發(fā)的事件,進(jìn)而把回調(diào)函數(shù)作為參與事件循環(huán)。
本篇文章主要是對(duì)Vue中的DOM異步更新策略和nextTick機(jī)制的解析,需要讀者有一定的Vue使用經(jīng)驗(yàn)并且熟悉掌握J(rèn)avaScript事件循環(huán)模型。引入:DOM的異步更新
{{test}}
export default { data () { return { test: "begin" }; }, methods () { handleClick () { this.test = "end"; console.log(this.$refs.test.innerText);//打印“begin” } } }
打印的結(jié)果是begin而不是我們?cè)O(shè)置的end。這個(gè)結(jié)果足以說(shuō)明Vue中DOM的更新并非同步。
在Vue官方文檔中是這樣說(shuō)明的:
可能你還沒(méi)有注意到,Vue異步執(zhí)行DOM更新。只要觀察到數(shù)據(jù)變化,Vue將開(kāi)啟一個(gè)隊(duì)列,并緩沖在同一事件循環(huán)中發(fā)生的所有數(shù)據(jù)改變。如果同一個(gè)watcher被多次觸發(fā),只會(huì)被推入到隊(duì)列中一次。這種在緩沖時(shí)去除重復(fù)數(shù)據(jù)對(duì)于避免不必要的計(jì)算和DOM操作上非常重要。然后,在下一個(gè)的事件循環(huán)“tick”中,Vue刷新隊(duì)列并執(zhí)行實(shí)際 (已去重的) 工作。
簡(jiǎn)而言之,就是在一個(gè)事件循環(huán)中發(fā)生的所有數(shù)據(jù)改變都會(huì)在下一個(gè)事件循環(huán)的Tick中來(lái)觸發(fā)視圖更新,這也是一個(gè)“批處理”的過(guò)程。(注意下一個(gè)事件循環(huán)的Tick有可能是在當(dāng)前的Tick微任務(wù)執(zhí)行階段執(zhí)行,也可能是在下一個(gè)Tick執(zhí)行,主要取決于nextTick函數(shù)到底是使用Promise/MutationObserver還是setTimeout)
Watcher隊(duì)列在Watcher的源碼中,我們發(fā)現(xiàn)watcher的update其實(shí)是異步的。(注:sync屬性默認(rèn)為false,也就是異步)
update () { /* istanbul ignore else */ if (this.lazy) { this.dirty = true } else if (this.sync) { /*同步則執(zhí)行run直接渲染視圖*/ this.run() } else { /*異步推送到觀察者隊(duì)列中,下一個(gè)tick時(shí)調(diào)用。*/ queueWatcher(this) } }
queueWatcher(this)函數(shù)的代碼如下:
/*將一個(gè)觀察者對(duì)象push進(jìn)觀察者隊(duì)列,在隊(duì)列中已經(jīng)存在相同的id則該觀察者對(duì)象將被跳過(guò),除非它是在隊(duì)列被刷新時(shí)推送*/ export function queueWatcher (watcher: Watcher) { /*獲取watcher的id*/ const id = watcher.id /*檢驗(yàn)id是否存在,已經(jīng)存在則直接跳過(guò),不存在則標(biāo)記哈希表has,用于下次檢驗(yàn)*/ if (has[id] == null) { has[id] = true if (!flushing) { /*如果沒(méi)有flush掉,直接push到隊(duì)列中即可*/ queue.push(watcher) } else { ... } // queue the flush if (!waiting) { waiting = true nextTick(flushSchedulerQueue) } } }
這段源碼有幾個(gè)需要注意的地方:
首先需要知道的是watcher執(zhí)行update的時(shí)候,默認(rèn)情況下肯定是異步的,它會(huì)做以下的兩件事:
判斷has數(shù)組中是否有這個(gè)watcher的id
如果有的話是不需要把watcher加入queue中的,否則不做任何處理。
這里面的nextTick(flushSchedulerQueue)中,flushScheduleQueue函數(shù)的作用主要是執(zhí)行視圖更新的操作,它會(huì)把queue中所有的watcher取出來(lái)并執(zhí)行相應(yīng)的視圖更新。
核心其實(shí)是nextTick函數(shù)了,下面我們具體看一下nextTick到底有什么用。
nextTicknextTick函數(shù)其實(shí)做了兩件事情,一是生成一個(gè)timerFunc,把回調(diào)作為microTask或macroTask參與到事件循環(huán)中來(lái)。二是把回調(diào)函數(shù)放入一個(gè)callbacks隊(duì)列,等待適當(dāng)?shù)臅r(shí)機(jī)執(zhí)行。(這個(gè)時(shí)機(jī)和timerFunc不同的實(shí)現(xiàn)有關(guān))
首先我們先來(lái)看它是怎么生成一個(gè)timerFunc把回調(diào)作為microTask或macroTask的。
if (typeof Promise !== "undefined" && isNative(Promise)) { /*使用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 /*新建一個(gè)textNode的DOM對(duì)象,用MutationObserver綁定該DOM并指定回調(diào)函數(shù),在DOM變化的時(shí)候則會(huì)觸發(fā)回調(diào),該回調(diào)會(huì)進(jìn)入主線程(比任務(wù)隊(duì)列優(yōu)先執(zhí)行),即textNode.data = String(counter)時(shí)便會(huì)觸發(fā)回調(diào)*/ 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 */ /*使用setTimeout將回調(diào)推入任務(wù)隊(duì)列尾部*/ timerFunc = () => { setTimeout(nextTickHandler, 0) } }
值得注意的是,它會(huì)按照Promise、MutationObserver、setTimeout優(yōu)先級(jí)去調(diào)用傳入的回調(diào)函數(shù)。前兩者會(huì)生成一個(gè)microTask任務(wù),而后者會(huì)生成一個(gè)macroTask。(微任務(wù)和宏任務(wù))
之所以會(huì)設(shè)置這樣的優(yōu)先級(jí),主要是考慮到瀏覽器之間的兼容性(IE沒(méi)有內(nèi)置Promise)。另外,設(shè)置Promise最優(yōu)先是因?yàn)?b>Promise.resolve().then回調(diào)函數(shù)屬于一個(gè)微任務(wù),瀏覽器在一個(gè)Tick中執(zhí)行完macroTask后會(huì)清空當(dāng)前Tick所有的microTask再進(jìn)行UI渲染,把DOM更新的操作放在Tick執(zhí)行microTask的階段來(lái)完成,相比使用setTimeout生成的一個(gè)macroTask會(huì)少一次UI的渲染。
而nextTickHandler函數(shù),其實(shí)才是我們真正要執(zhí)行的函數(shù)。
function nextTickHandler () { pending = false /*執(zhí)行所有callback*/ const copies = callbacks.slice(0) callbacks.length = 0 for (let i = 0; i < copies.length; i++) { copies[i]() } }
這里的callbacks變量供nextTickHandler消費(fèi)。而前面我們所說(shuō)的nextTick函數(shù)第二點(diǎn)功能中“等待適當(dāng)?shù)臅r(shí)機(jī)執(zhí)行”,其實(shí)就是因?yàn)?b>timerFunc的實(shí)現(xiàn)方式有差異,如果是PromiseMutationObserver則nextTickHandler回調(diào)是一個(gè)microTask,它會(huì)在當(dāng)前Tick的末尾來(lái)執(zhí)行。如果是setTiemout則nextTickHandler回調(diào)是一個(gè)macroTask,它會(huì)在下一個(gè)Tick來(lái)執(zhí)行。
還有就是callbacks中的成員是如何被push進(jìn)來(lái)的?從源碼中我們可以知道,nextTick是一個(gè)自執(zhí)行的函數(shù),一旦執(zhí)行是return了一個(gè)queueNextTick,所以我們?cè)谡{(diào)用nextTick其實(shí)就是在調(diào)用queueNextTick這個(gè)函數(shù)。它的源代碼如下:
return function queueNextTick (cb?: Function, ctx?: Object) { let _resolve /*cb存到callbacks中*/ 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 }) } }
可以看到,一旦調(diào)用nextTick函數(shù)時(shí)候,傳入的function就會(huì)被存放到callbacks閉包中,然后這個(gè)callbacks由nextTickHandler消費(fèi),而nextTickHandler的執(zhí)行時(shí)間又是由timerFunc來(lái)決定。
我們?cè)倩貋?lái)看Watcher中的一段代碼:
/*將一個(gè)觀察者對(duì)象push進(jìn)觀察者隊(duì)列,在隊(duì)列中已經(jīng)存在相同的id則該觀察者對(duì)象將被跳過(guò),除非它是在隊(duì)列被刷新時(shí)推送*/ export function queueWatcher (watcher: Watcher) { /*獲取watcher的id*/ const id = watcher.id /*檢驗(yàn)id是否存在,已經(jīng)存在則直接跳過(guò),不存在則標(biāo)記哈希表has,用于下次檢驗(yàn)*/ if (has[id] == null) { has[id] = true if (!flushing) { /*如果沒(méi)有flush掉,直接push到隊(duì)列中即可*/ queue.push(watcher) } else { ... } // queue the flush if (!waiting) { waiting = true nextTick(flushSchedulerQueue) } } }
這里面的nextTick(flushSchedulerQueue)中的flushSchedulerQueue函數(shù)其實(shí)就是watcher的視圖更新。調(diào)用的時(shí)候會(huì)把它push到callbacks中來(lái)異步執(zhí)行。
另外,關(guān)于waiting變量,這是很重要的一個(gè)標(biāo)志位,它保證flushSchedulerQueue回調(diào)只允許被置入callbacks一次。
也就是說(shuō),默認(rèn)waiting變量為false,執(zhí)行一次后waiting為true,后續(xù)的this.xxx不會(huì)再次觸發(fā)nextTick的執(zhí)行,而是把this.xxx相對(duì)應(yīng)的watcher推入flushSchedulerQueue的queue隊(duì)列中。
所以,也就是說(shuō)DOM確實(shí)是異步更新,但是具體是在下一個(gè)Tick更新還是在當(dāng)前Tick執(zhí)行microTask的時(shí)候更新,具體要看nextTcik的實(shí)現(xiàn)方式,也就是具體跑的是Promise/MutationObserver還是setTimeout。
附:nextTick源碼帶注釋,有興趣可以觀摩一下。
這里面使用Promise和setTimeout來(lái)執(zhí)行異步任務(wù)的方式都很好理解,比較巧妙的地方是利用MutationObserver執(zhí)行異步任務(wù)。MutationObserver是H5的新特性,它能夠監(jiān)聽(tīng)指定范圍內(nèi)的DOM變化并執(zhí)行其回調(diào),它的回調(diào)會(huì)被當(dāng)作microTask來(lái)執(zhí)行,具體參考MDN。
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) }
可以看到,通過(guò)借用MutationObserver來(lái)創(chuàng)建一個(gè)microTask。nextTickHandler作為回調(diào)傳入MutationObserver中。
這里面創(chuàng)建了一個(gè)textNode作為觀測(cè)的對(duì)象,當(dāng)timerFunc執(zhí)行的時(shí)候,textNode.data會(huì)發(fā)生改變,便會(huì)觸發(fā)MutatinObservers的回調(diào)函數(shù),而這個(gè)函數(shù)才是我們真正要執(zhí)行的任務(wù),它是一個(gè)microTask。
注:2.5+版本的Vue對(duì)nextTick進(jìn)行了修改,具體參考下面“版本升級(jí)”一節(jié)。
關(guān)于Vue暴露的全局nextTick繼續(xù)來(lái)看下面的這段代碼:
{{test}}
var vm = new Vue({ el: "#example", data: { test: "begin", }, methods: { handleClick() { this.test = "end"; console.log("1") setTimeout(() => { // macroTask console.log("3") }, 0); Promise.resolve().then(function() { //microTask console.log("promise!") }) this.$nextTick(function () { console.log("2") }) } } })
在Chrome下,這段代碼執(zhí)行的順序的1、2、promise、3。
可能有同學(xué)會(huì)以為這是1、promise、2、3,其實(shí)是忽略了一個(gè)標(biāo)志位pending。
我們回到nextTick函數(shù)return的queueNextTick可以發(fā)現(xiàn):
return function queueNextTick (cb?: Function, ctx?: Object) { let _resolve /*cb存到callbacks中*/ 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 }) } }
這里面通過(guò)對(duì)pending的判斷來(lái)檢測(cè)是否已經(jīng)有timerFunc這個(gè)函數(shù)在事件循環(huán)的任務(wù)隊(duì)列等待被執(zhí)行。如果存在的話,那么是不會(huì)再重復(fù)執(zhí)行的。
最后異步執(zhí)行nextTickHandler時(shí)又會(huì)把pending置為false。
function nextTickHandler () { pending = false /*執(zhí)行所有callback*/ const copies = callbacks.slice(0) callbacks.length = 0 for (let i = 0; i < copies.length; i++) { copies[i]() } }
所以回到我們的例子:
handleClick() { this.test = "end"; console.log("1") setTimeout(() => { // macroTask console.log("3") }, 0); Promise.resolve().then(function() { //microTask console.log("promise!") }); this.$nextTick(function () { console.log("2") }); }
代碼中,this.test = "end"必然會(huì)觸發(fā)watcher進(jìn)行視圖的重新渲染,而我們?cè)谖恼碌?b>Watcher一節(jié)中就已經(jīng)有提到會(huì)調(diào)用nextTick函數(shù),一開(kāi)始pending變量肯定就是false,因此它會(huì)被修改為true并且執(zhí)行timerFunc。之后執(zhí)行this.$nextTick其實(shí)還是調(diào)用的nextTick函數(shù),只不過(guò)此時(shí)的pending為true說(shuō)明timerFunc已經(jīng)被生成,所以this.$nextTick(fn)只是把傳入的fn置入callbacks之中。此時(shí)的callbacks有兩個(gè)function成員,一個(gè)是flushSchedulerQueue,另外一個(gè)就是this.$nextTick()的回調(diào)。
因此,上面這段代碼中,在Chrome下,有一個(gè)macroTask和兩個(gè)microTask。一個(gè)macroTask就是setTimeout,兩個(gè)microTask:分別是Vue的timerFunc(其中先后執(zhí)行flushSchedulerQueue和function() {console.log("2")})、代碼中的Promise.resolve().then()。
版本升級(jí)帶來(lái)的變化上面討論的nextTick實(shí)現(xiàn)是2.4版本以下的實(shí)現(xiàn),2.5以上版本對(duì)于nextTick的內(nèi)部實(shí)現(xiàn)進(jìn)行了大量的修改。
獨(dú)立文件首先是從Vue 2.5+開(kāi)始,抽出來(lái)了一個(gè)多帶帶的文件next-tick.js來(lái)執(zhí)行它。
microTask與macroTask在代碼中,有這么一段注釋:
其大概的意思就是:在Vue 2.4之前的版本中,nextTick幾乎都是基于microTask實(shí)現(xiàn)的(具體可以看文章nextTick一節(jié)),但是由于microTask的執(zhí)行優(yōu)先級(jí)非常高,在某些場(chǎng)景之下它甚至要比事件冒泡還要快,就會(huì)導(dǎo)致一些詭異的問(wèn)題;但是如果全部都改成macroTask,對(duì)一些有重繪和動(dòng)畫(huà)的場(chǎng)景也會(huì)有性能的影響。所以最終nextTick采取的策略是默認(rèn)走microTask,對(duì)于一些DOM的交互事件,如v-on綁定的事件回調(diào)處理函數(shù)的處理,會(huì)強(qiáng)制走macroTask。
具體做法就是,在Vue執(zhí)行綁定的DOM事件時(shí),默認(rèn)會(huì)給回調(diào)的handler函數(shù)調(diào)用withMacroTask方法做一層包裝,它保證整個(gè)回調(diào)函數(shù)的執(zhí)行過(guò)程中,遇到數(shù)據(jù)狀態(tài)的改變,這些改變而導(dǎo)致的視圖更新(DOM更新)的任務(wù)都會(huì)被推到macroTask。
function add$1 (event, handler, once$$1, capture, passive) { handler = withMacroTask(handler); if (once$$1) { handler = createOnceHandler(handler, event, capture); } target$1.addEventListener( event, handler, supportsPassive ? { capture: capture, passive: passive } : capture ); } /** * Wrap a function so that if any code inside triggers state change, * the changes are queued using a Task instead of a MicroTask. */ function withMacroTask (fn) { return fn._withTask || (fn._withTask = function () { useMacroTask = true; var res = fn.apply(null, arguments); useMacroTask = false; return res }) }
而對(duì)于macroTask的執(zhí)行,Vue優(yōu)先檢測(cè)是否支持原生setImmediate(高版本IE和Edge支持),不支持的話再去檢測(cè)是否支持原生MessageChannel,如果還不支持的話為setTimeout(fn, 0)。
最后,寫(xiě)一段demo來(lái)測(cè)試一下:
{{test}}
var vm = new Vue({ el: "#example", data: { test: "begin", }, methods: { handleClick: function() { this.test = end; console.log("script") this.$nextTick(function () { console.log("nextTick") }) Promise.resolve().then(function () { console.log("promise") }) } } })
在Vue 2.5+中,這段代碼的輸出順序是script、promise、nextTick,而Vue 2.4輸出script、nextTick、promise。nextTick執(zhí)行順序的差異正好說(shuō)明了上面的改變。
MessageChannel在Vue 2.4版本以前使用的MutationObserver來(lái)模擬異步任務(wù)。而Vue 2.5版本以后,由于兼容性棄用了MutationObserver。
Vue 2.5+版本使用了MessageChannel來(lái)模擬macroTask。除了IE以外,messageChannel的兼容性還是比較可觀的。
const channel = new MessageChannel() const port = channel.port2 channel.port1.onmessage = flushCallbacks macroTimerFunc = () => { port.postMessage(1) }
可見(jiàn),新建一個(gè)MessageChannel對(duì)象,該對(duì)象通過(guò)port1來(lái)檢測(cè)信息,port2發(fā)送信息。通過(guò)port2的主動(dòng)postMessage來(lái)觸發(fā)port1的onmessage事件,進(jìn)而把回調(diào)函數(shù)flushCallbacks作為macroTask參與事件循環(huán)。
MessageChannel VS setTimeout為什么要優(yōu)先MessageChannel創(chuàng)建macroTask而不是setTimeout?
HTML5中規(guī)定setTimeout的最小時(shí)間延遲是4ms,也就是說(shuō)理想環(huán)境下異步回調(diào)最快也是4ms才能觸發(fā)。
Vue使用這么多函數(shù)來(lái)模擬異步任務(wù),其目的只有一個(gè),就是讓回調(diào)異步且盡早調(diào)用。而MessageChannel的延遲明顯是小于setTimeout的。對(duì)比傳送門(mén)
為什么要異步更新視圖看下面的代碼:
{{test}}
export default { data () { return { test: 0 }; }, mounted () { for(let i = 0; i < 1000; i++) { this.test++; } } }
現(xiàn)在有這樣的一種情況,mounted的時(shí)候test的值會(huì)被++循環(huán)執(zhí)行1000次。 每次++時(shí),都會(huì)根據(jù)響應(yīng)式觸發(fā)setter->Dep->Watcher->update->run。 如果這時(shí)候沒(méi)有異步更新視圖,那么每次++都會(huì)直接操作DOM更新視圖,這是非常消耗性能的。 所以Vue實(shí)現(xiàn)了一個(gè)queue隊(duì)列,在下一個(gè)Tick(或者是當(dāng)前Tick的微任務(wù)階段)的時(shí)候會(huì)統(tǒng)一執(zhí)行queue中Watcher的run。同時(shí),擁有相同id的Watcher不會(huì)被重復(fù)加入到該queue中去,所以不會(huì)執(zhí)行1000次Watcher的run。最終更新視圖只會(huì)直接將test對(duì)應(yīng)的DOM的0變成1000。 保證更新視圖操作DOM的動(dòng)作是在當(dāng)前棧執(zhí)行完以后下一個(gè)Tick(或者是當(dāng)前Tick的微任務(wù)階段)的時(shí)候調(diào)用,大大優(yōu)化了性能。
應(yīng)用場(chǎng)景在操作DOM節(jié)點(diǎn)無(wú)效的時(shí)候,就要考慮操作的實(shí)際DOM節(jié)點(diǎn)是否存在,或者相應(yīng)的DOM是否被更新完畢。
比如說(shuō),在created鉤子中涉及DOM節(jié)點(diǎn)的操作肯定是無(wú)效的,因?yàn)榇藭r(shí)還沒(méi)有完成相關(guān)DOM的掛載。解決的方法就是在nextTick函數(shù)中去處理DOM,這樣才能保證DOM被成功掛載而有效操作。
還有就是在數(shù)據(jù)變化之后要執(zhí)行某個(gè)操作,而這個(gè)操作需要使用隨數(shù)據(jù)改變而改變的DOM時(shí),這個(gè)操作應(yīng)該放進(jìn)Vue.nextTick。
之前在做慕課網(wǎng)音樂(lè)webApp的時(shí)候關(guān)于播放器內(nèi)核的開(kāi)發(fā)就涉及到了這個(gè)問(wèn)題。下面我把問(wèn)題簡(jiǎn)化:
現(xiàn)在我們要實(shí)現(xiàn)一個(gè)需求是點(diǎn)擊按鈕變換audio標(biāo)簽的src屬性來(lái)實(shí)現(xiàn)切換歌曲。
const musicList = [ "http://sc1.111ttt.cn:8282/2017/1/11m/11/304112003137.m4a?tflag=1519095601&pin=6cd414115fdb9a950d827487b16b5f97#.mp3", "http://sc1.111ttt.cn:8282/2017/1/11m/11/304112002493.m4a?tflag=1519095601&pin=6cd414115fdb9a950d827487b16b5f97#.mp3", "http://sc1.111ttt.cn:8282/2017/1/11m/11/304112004168.m4a?tflag=1519095601&pin=6cd414115fdb9a950d827487b16b5f97#.mp3" ]; var vm = new Vue({ el: "#example", data: { index: 0, url: "" }, methods: { changeUrl() { this.index = (this.index + 1) % musicList.length this.url = musicList[this.index]; this.$refs.audio.play(); } } });
毫無(wú)懸念,這樣肯定是會(huì)報(bào)錯(cuò)的:
Uncaught (in promise) DOMException: The element has no supported sources.
原因就在于audio.play()是同步的,而這個(gè)時(shí)候DOM更新是異步的,src屬性還沒(méi)有被更新,結(jié)果播放的時(shí)候src屬性為空,就報(bào)錯(cuò)了。
解決辦法就是在play的操作加上this.$nextTick()。
this.$nextTick(function() { this.$refs.audio.play(); });
參考鏈接
https://github.com/youngwind/...
https://github.com/answershut...
https://juejin.im/post/5a1af8...
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/107544.html
摘要:而和的延遲明顯是小于的。因?yàn)榈氖录C(jī)制是通過(guò)事件隊(duì)列來(lái)調(diào)度執(zhí)行,會(huì)等主進(jìn)程執(zhí)行空閑后進(jìn)行調(diào)度,所以先回去等待所有的進(jìn)程執(zhí)行完成之后再去一次更新。因?yàn)槭紫扔|發(fā)了,導(dǎo)致觸發(fā)了的,從而將更新操作進(jìn)入的事件隊(duì)列。這種情況會(huì)導(dǎo)致順序成為了。 背景 我們先來(lái)看一段Vue的執(zhí)行代碼: export default { data () { return { msg: 0 ...
摘要:而和的延遲明顯是小于的。因?yàn)榈氖录C(jī)制是通過(guò)事件隊(duì)列來(lái)調(diào)度執(zhí)行,會(huì)等主進(jìn)程執(zhí)行空閑后進(jìn)行調(diào)度,所以先回去等待所有的進(jìn)程執(zhí)行完成之后再去一次更新。因?yàn)槭紫扔|發(fā)了,導(dǎo)致觸發(fā)了的,從而將更新操作進(jìn)入的事件隊(duì)列。這種情況會(huì)導(dǎo)致順序成為了。 背景 我們先來(lái)看一段Vue的執(zhí)行代碼: export default { data () { return { msg: 0 ...
摘要:核心的異步延遲函數(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,...
摘要:我們發(fā)現(xiàn)默認(rèn)是使用異步執(zhí)行更新。優(yōu)先使用,在不存在的情況下使用,這兩個(gè)方法的回調(diào)函數(shù)都會(huì)在中執(zhí)行,它們會(huì)比更早執(zhí)行,所以優(yōu)先使用。是最后的一種備選方案,它會(huì)將回調(diào)函數(shù)加入中,等到執(zhí)行。 寫(xiě)在前面 因?yàn)閷?duì)Vue.js很感興趣,而且平時(shí)工作的技術(shù)棧也是Vue.js,這幾個(gè)月花了些時(shí)間研究學(xué)習(xí)了一下Vue.js源碼,并做了總結(jié)與輸出。文章的原地址:https://github.com/ans...
閱讀 2767·2021-11-22 13:54
閱讀 2704·2021-10-14 09:42
閱讀 4053·2021-09-28 09:47
閱讀 2174·2021-09-03 10:28
閱讀 1217·2021-07-26 23:38
閱讀 2568·2019-08-30 15:54
閱讀 2649·2019-08-29 16:35
閱讀 1438·2019-08-29 15:42