国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

Vue源碼詳解之nextTick:MutationObserver只是浮云,microtask才是核

陳偉 / 1628人閱讀

摘要:后來尤雨溪了解到是將回調(diào)放入的隊列。而且瀏覽器內(nèi)部為了更快的響應(yīng)用戶,內(nèi)部可能是有多個的而的的優(yōu)先級可能更高,因此對于尤雨溪采用的,甚至可能已經(jīng)多次執(zhí)行了的,都沒有執(zhí)行的,也就導(dǎo)致了我們更新操

原發(fā)于我的博客。

前一篇文章已經(jīng)詳細記述了Vue的核心執(zhí)行過程。相當(dāng)于已經(jīng)搞定了主線劇情。后續(xù)的文章都會對其中沒有介紹的細節(jié)進行展開。

現(xiàn)在我們就來講講其他支線任務(wù):nextTick和microtask。

Vue的nextTick api的實現(xiàn)部分是Vue里比較好理解的一部分,與其他部分的代碼也非常的解耦,因此這一塊的相關(guān)源碼解析文章很多。我本來也不準備多帶帶寫博客細說這部分,但是最近偶然在別人的文章中了解到:
每輪次的event loop中,每次執(zhí)行一個task,并執(zhí)行完microtask隊列中的所有microtask之后,就會進行UI的渲染。但是作者似乎對于這個結(jié)論也不是很肯定。而我第一反應(yīng)就是Vue的$nextTick既然用到了MutationObserver(MO的回調(diào)放進的是microtask的任務(wù)隊列中的),那么是不是也是出于這個考慮呢?于是我想研究了一遍Vue的$nextTick,就可以了解是不是出于這個目的,也同時看能不能佐證UI Render真的是在microtask隊列清空后執(zhí)行的。

研究之后的結(jié)論:我之前對于$nextTick源碼的理解完全是錯的,以及每輪事件循環(huán)執(zhí)行完所有的microtask,是會執(zhí)行UI Render的。

task/macrotask和microtask的概念自從去年知乎上有人提出這個問題之后,task和microtask已經(jīng)被很多同學(xué)了解了,我也是當(dāng)時看到了microtask的內(nèi)容,現(xiàn)在已經(jīng)有非常多的中文介紹博客在介紹這部分的知識,最近這篇火遍掘金、SF和知乎的文章,最后也是考了microtask的概念。如果你沒有看過task/microtask的內(nèi)容的話,我還是推薦這篇英文博客,是絕大多數(shù)國內(nèi)博客的內(nèi)容來源。

先說nextTick的具體實現(xiàn)

先用120秒介紹MutationObserver: MO是HTML5中的新API,是個用來監(jiān)視DOM變動的接口。他能監(jiān)聽一個DOM對象上發(fā)生的子節(jié)點刪除、屬性修改、文本內(nèi)容修改等等。
調(diào)用過程很簡單,但是有點不太尋常:你需要先給他綁回調(diào):
var mo = new MutationObserver(callback)
通過給MO的構(gòu)造函數(shù)傳入一個回調(diào),能得到一個MO實例,這個回調(diào)就會在MO實例監(jiān)聽到變動時觸發(fā)。

這個時候你只是給MO實例綁定好了回調(diào),他具體監(jiān)聽哪個DOM、監(jiān)聽節(jié)點刪除還是監(jiān)聽屬性修改,你都還沒有設(shè)置。而調(diào)用他的observer方法就可以完成這一步:

var domTarget = 你想要監(jiān)聽的dom節(jié)點
mo.observe(domTarget, {
      characterData: true //說明監(jiān)聽文本內(nèi)容的修改。
})

一個需要先說的細節(jié)是,MutationObserver的回調(diào)是放在microtask中執(zhí)行的。

ok了,現(xiàn)在這個domTarget上發(fā)生的文本內(nèi)容修改就會被mo監(jiān)聽到,mo就會觸發(fā)你在new MutationObserver(callback)中傳入的callback。

現(xiàn)在我們來看Vue.nextTick的源碼:

export const nextTick = (function () {
  var callbacks = []
  var pending = false
  var timerFunc
  function nextTickHandler () {
    pending = false
    // 之所以要slice復(fù)制一份出來是因為有的cb執(zhí)行過程中又會往callbacks中加入內(nèi)容
    // 比如$nextTick的回調(diào)函數(shù)里又有$nextTick
    // 這些是應(yīng)該放入到下一個輪次的nextTick去執(zhí)行的,
    // 所以拷貝一份當(dāng)前的,遍歷執(zhí)行完當(dāng)前的即可,避免無休止的執(zhí)行下去
    var copies = callbacks.slice(0)
    callbacks = []
    for (var i = 0; i < copies.length; i++) {
      copies[i]()
    }
  }

  /* istanbul ignore if */
  // ios9.3以上的WebView的MutationObserver有bug,
  //所以在hasMutationObserverBug中存放了是否是這種情況
  if (typeof MutationObserver !== "undefined" && !hasMutationObserverBug) {
    var counter = 1
    // 創(chuàng)建一個MutationObserver,observer監(jiān)聽到dom改動之后后執(zhí)行回調(diào)nextTickHandler
    var observer = new MutationObserver(nextTickHandler)
    var textNode = document.createTextNode(counter)
    // 調(diào)用MutationObserver的接口,觀測文本節(jié)點的字符內(nèi)容
    observer.observe(textNode, {
      characterData: true
    })
    // 每次執(zhí)行timerFunc都會讓文本節(jié)點的內(nèi)容在0/1之間切換,
    // 不用true/false可能是有的瀏覽器對于文本節(jié)點設(shè)置內(nèi)容為true/false有bug?
    // 切換之后將新值賦值到那個我們MutationObserver觀測的文本節(jié)點上去
    timerFunc = function () {
      counter = (counter + 1) % 2
      textNode.data = counter
    }
  } else {
    // webpack attempts to inject a shim for setImmediate
    // if it is used as a global, so we have to work around that to
    // avoid bundling unnecessary code.
    // webpack默認會在代碼中插入setImmediate的墊片
    // 沒有MutationObserver就優(yōu)先用setImmediate,不行再用setTimeout
    const context = inBrowser
      ? window
      : typeof global !== "undefined" ? global : {}
    timerFunc = context.setImmediate || setTimeout
  }
  return function (cb, ctx) {
    var func = ctx
      ? function () { cb.call(ctx) }
      : cb
    callbacks.push(func)
    // 如果pending為true, 就其實表明本輪事件循環(huán)中已經(jīng)執(zhí)行過timerFunc(nextTickHandler, 0)
    if (pending) return
    pending = true
    timerFunc(nextTickHandler, 0)
  }
})()

上面這個函數(shù)執(zhí)行過程后生成的那個函數(shù)才是nextTick。而這個函數(shù)的執(zhí)行過程就是先初始化pending變量和cb變量,cb用來存放需要執(zhí)行的回調(diào),pending表示是否把清空回調(diào)的nextTickHandler函數(shù)加入到異步隊列中。

然后就是創(chuàng)建了一個MO,這個MO監(jiān)聽了一個新創(chuàng)建的文本節(jié)點的文本內(nèi)容變化,同時監(jiān)聽到變化時的回調(diào)就是nextTickHandler。nextTickHandler遍歷cb數(shù)組,把需要執(zhí)行的cb給拿出來一個個執(zhí)行了。

而最后返回出去作為nextTick的那個函數(shù)就比較簡單了:

function (cb, ctx) {
    var func = ctx
      ? function () { cb.call(ctx) }
      : cb
    callbacks.push(func)
    // 如果pending為true, 就其實表明本輪事件循環(huán)中已經(jīng)執(zhí)行過timerFunc(nextTickHandler, 0)
    if (pending) return
    pending = true
    timerFunc(nextTickHandler, 0)
  }
}

也就是把傳入的回調(diào)放入cb數(shù)組當(dāng)中,然后執(zhí)行timerFunc(nextTickHandler, 0),其實是執(zhí)行timerFunc(),后面?zhèn)魅氲膬蓞?shù)沒用,在瀏覽器不支持MO的情況timerFunc才回退到setTimeout,那倆參數(shù)才有效果。timerFunc就是把那個被MO監(jiān)聽的文本節(jié)點改一下它的內(nèi)容,這樣我改了文本內(nèi)容,MO就會在當(dāng)前的所有同步代碼完成之后執(zhí)行回調(diào),從而執(zhí)行數(shù)據(jù)更新到DOM上之后的任務(wù)。

我一開始在看這一段代碼時忘記了MutationObserver的回調(diào)是在microtask里執(zhí)行的。而且當(dāng)時也還沒有看過Vue的其他源碼,當(dāng)時的我大體看懂nextTick代碼流程之后,形成了如下的理解,而且覺得似乎完美的解釋了代碼邏輯:
watcher監(jiān)聽到數(shù)據(jù)變化之后,會立馬去修改dom,接著用戶書寫的代碼里的nextTick被執(zhí)行,而nextTick內(nèi)部也是去修改DOM(textNode),當(dāng)這個最后修改的textNode修改完成了,觸發(fā)了MutationObserver的回調(diào),那就意味著,前面的DOM修改也已經(jīng)完成了,所以nextTick向用戶保證的DOM更新之后再執(zhí)行用戶的回調(diào)就得以實現(xiàn)了。

Damn,現(xiàn)在看了Batcher的代碼和認真反思了以后,立馬醒悟,上面的想法完完全全就是一坨狗屎,totally shit!

首先,一個普遍的常識是DOM Tree的修改是實時的,而修改的Render到DOM上才是異步的。根本不存在什么所謂的等待DOM修改完成,任何時候我在上一行代碼里往DOM中添加了一個元素、修改了一個DOM的textContent,你在下一行代碼里一定能立馬就讀取到新的DOM,我知道這個理。但是我還是搞不懂我怎么會產(chǎn)生用nextTick來保證DOM修改的完成這樣的怪念頭。可能那天屎吃得有點多了。

其次,我們來看看使用nextTick的真正原因:

Vue在兩個地方用到了上述nextTick:

Vue.nextTick和Vue.prototype.$nextTick都是直接使用了這個nextTick

在batcher中,也就是watcher觀測到數(shù)據(jù)變化后執(zhí)行的是nextTick(flushBatcherQueue)flushBatcherQueue則負責(zé)執(zhí)行完成所有的dom更新操作。

Batcher的源碼,我在上一篇文章當(dāng)中已經(jīng)詳細的分析了,在這里我用一張圖來說明它和nextTick的詳細處理過程吧。
假設(shè)此時Vue實例的模板為:

{{a}}

仔細跟蹤了代碼執(zhí)行過程我們會發(fā)現(xiàn),真正的去遍歷watcher,批處理更新是在microtask中執(zhí)行的,而且用戶在修改數(shù)據(jù)后自己執(zhí)行的nextTick(cb)也會在此時執(zhí)行cb,他們都是在同一個microtask中執(zhí)行。根本就不是我最開始想的那樣,把回調(diào)放在以后的事件循環(huán)中去執(zhí)行。

同時,上面這個過程也深切的揭露出Vue nextTick的本質(zhì),我不是想要MO來幫我真正監(jiān)聽DOM更改,我只是想要一個異步API,用來在當(dāng)前的同步代碼執(zhí)行完畢后,執(zhí)行我想執(zhí)行的異步回調(diào)。

之所以要這樣,是因為用戶的代碼當(dāng)中是可能多次修改數(shù)據(jù)的,而每次修改都會同步通知到所有訂閱該數(shù)據(jù)的watcher,而立馬執(zhí)行將數(shù)據(jù)寫到DOM上是肯定不行的,那就只是把watcher加入數(shù)組。等到當(dāng)前task執(zhí)行完畢,所有的同步代碼已經(jīng)完成,那么這一輪次的數(shù)據(jù)修改就已經(jīng)結(jié)束了,這個時候我可以安安心心的去將對監(jiān)聽到依賴變動的watcher完成數(shù)據(jù)真正寫入到DOM上的操作,這樣即使你在之前的task里改了一個watcher的依賴100次,我最終只會計算一次value、改DOM一次。一方面省去了不必要的DOM修改,另一方面將DOM操作聚集,可以提升DOM Render效率。

那為什么一定要用MutationObserver呢?不,并沒有一定要用MO,只要是microtask都可以。在最新版的Vue源碼里,優(yōu)先使用的就是Promise.resolve().then(nextTickHandler)來將異步回調(diào)放入到microtask中(MO在IOS9.3以上的WebView中有bug),沒有原生Promise才用MO。

這充分說明了microtask才是nextTick的本質(zhì),MO什么的只是個備胎,要是有比MO優(yōu)先級更高、瀏覽器兼容性更好的microtask,那可能就分分鐘把MO拿下了。

那問題又來了,為什么一定要microtask?task可以嗎?(macrotask和task是一回事哈,HTML5標準里甚至都沒有macrotask這個詞)。

哈,現(xiàn)在剛好有個例子,Vue一開始曾經(jīng)改過nextTick的實現(xiàn)。我們來看看這兩個jsFiddle:jsfiddle1和jsfiddle2。

兩個fiddle的實現(xiàn)一模一樣,就是讓那個絕對定位的黃色元素起到一個fixed定位的效果:綁定scroll事件,每次滾動的時候,計算當(dāng)前滾動的位置并更改到那個絕對定位元素的top屬性上去。大家自己試試滾動幾下,對比下效果,你就會發(fā)現(xiàn)第一個fiddle中的黃元素是穩(wěn)定不動的,fixed很好。而后一個fiddle中就有問題了,黃色元素上下晃動,似乎跟不上我們scroll的節(jié)奏,總要慢一點,雖然最后停下滾動時位置是對的。

上述兩個例子其實是在這個issue中找到的,第一個jsfiddle使用的版本是Vue 2.0.0-rc.6,這個版本的nextTick實現(xiàn)是采用了MO,而后因為IOS9.3的WebView里的MO有bug,于是尤雨溪更改了實現(xiàn),換成了window.postMessage,也就是后一個fiddle所使用的Vue 2.0.0-rc.7。后來尤雨溪了解到window.postMessage是將回調(diào)放入的macrotask 隊列。這就是問題的根源了。

HTML中的UI事件、網(wǎng)絡(luò)事件、HTML Parsing等都是使用的task來完成,因此每次scroll事件觸發(fā)后,在當(dāng)前的task里只是完成了把watcher加入隊列和把清空watcher的flushBatcherQueue作為異步回調(diào)傳入nextTick。

如果nextTick使用的是microtask,那么在task執(zhí)行完畢之后就會立即執(zhí)行所有microtask,那么flushBatcherQueue(真正修改DOM)便得以在此時立即完成,而后,當(dāng)前輪次的microtask全部清理完成時,執(zhí)行UI rendering,把重排重繪等操作真正更新到DOM上(后文會細說)。(注意,頁面的滾動效果并不需要重繪哈。重繪是當(dāng)你修改了UI樣式、DOM結(jié)構(gòu)等等,頁面將樣式呈現(xiàn)出來,別暈了。)
如果nextTick使用的是task,那么會在當(dāng)前的task和所有microtask執(zhí)行完畢之后才在以后的某一次task執(zhí)行過程中處理flushBatcherQueue,那個時候才真正執(zhí)行各個指令的修改DOM操作,但那時為時已晚,錯過了多次觸發(fā)重繪、渲染UI的時機。而且瀏覽器內(nèi)部為了更快的響應(yīng)用戶UI,內(nèi)部可能是有多個task queue的:

For example, a user agent could have one task queue for mouse and key events (the user interaction task source), and another for everything else. The user agent could then give keyboard and mouse events preference over other tasks three quarters of the time, keeping the interface responsive but not starving other task queues, and never processing events from any one task source out of order.

而UI的task queue的優(yōu)先級可能更高,因此對于尤雨溪采用的window.postMessage,甚至可能已經(jīng)多次執(zhí)行了UI的task,都沒有執(zhí)行window.postMessage的task,也就導(dǎo)致了我們更新DOM操作的延遲。在重CPU計算、UI渲染任務(wù)情況下,這一延遲達到issue觀測到的100毫秒到1秒的級別是完全課可能的。因此,使用task來實現(xiàn)nextTick是不可行的,而尤雨溪也撤回了這一次的修改,后續(xù)的nextTick實現(xiàn)中,依然是使用的Promise.then和MO。

task microtask和每輪event loop之后的UI Render

我最近認真閱讀了一下HTML5規(guī)范,還是來說一說task和microtask處理完成之后的UI渲染過程,講一下每次task執(zhí)行和所有microtask執(zhí)行完畢后使如何完成UI Render的。

先上HTML標準原文:
比較典型的task有如下這些

Events
Dispatching an Event object at a particular EventTarget object is often done by a dedicated task. Not all events are dispatched using the task queue, many are dispatched during other tasks.

Parsing
The HTML parser tokenizing one or more bytes, and then processing any resulting tokens, is typically a task.

Callbacks
Calling a callback is often done by a dedicated task.

Using a resource
When an algorithm fetches a resource, if the fetching occurs in a non-blocking fashion then the processing of the resource once some or all of the resource is available is performed by a task.

Reacting to DOM manipulation
Some elements have tasks that trigger in response to DOM manipulation, e.g. when that element is inserted into the document.

此外,還包括setTimeout, setInterval, setImmediate, window.postMessage等等。
上述Reacting to DOM manipulation并不是說你執(zhí)行DOM操作時就會把這個DOM操作的執(zhí)行當(dāng)成一個task。是那些異步的reacting會被當(dāng)做task。

HTML5標準:task、microtask和UI render的具體執(zhí)行過程如下:

An event loop must continually run through the following steps for as long as it exists:

1.Select the oldest task on one of the event loop"s task queues, if any, ignoring, in the case of a browsing context event loop, tasks whose associated Documents are not fully active. The user agent may pick any task queue. If there is no task to select, then jump to the microtasks step below.

2.Set the event loop"s currently running task to the task selected in the previous step.

3.Run: Run the selected task.

4.Set the event loop"s currently running task back to null.

5.Remove the task that was run in the run step above from its task queue.

6.Microtasks: Perform a microtask checkpoint. //這里會執(zhí)行所有的microtask

7.Update the rendering: If this event loop is a browsing context event loop (as opposed to a worker event loop), then run the following substeps.

7.1 Let now be the value that would be returned by the Performance object"s now() method.
7.2 Let docs be the list of Document objects associated with the event loop in question, sorted arbitrarily except that the following conditions must be met:
7.3 If there are top-level browsing contexts B that the user agent believes would not benefit from having their rendering updated at this time, then remove from docs all Document objects whose browsing context"s top-level browsing context is in B.
7.4 If there are a nested browsing contexts B that the user agent believes would not benefit from having their rendering updated at this time, then remove from docs all Document objects whose browsing context is in B.
7.5 For each fully active Document in docs, run the resize steps for that Document, passing in now as the timestamp. [CSSOMVIEW]
7.6 For each fully active Document in docs, run the scroll steps for that Document, passing in now as the timestamp. [CSSOMVIEW]
7.7 For each fully active Document in docs, evaluate media queries and report changes for that Document, passing in now as the timestamp. [CSSOMVIEW]
7.8 For each fully active Document in docs, run CSS animations and send events for that Document, passing in now as the timestamp. [CSSANIMATIONS]
7.9 For each fully active Document in docs, run the fullscreen rendering steps for that Document, passing in now as the timestamp. [FULLSCREEN]
7.10 For each fully active Document in docs, run the animation frame callbacks for that Document, passing in now as the timestamp.
7.11 For each fully active Document in docs, run the update intersection observations steps for that Document, passing in now as the timestamp. [INTERSECTIONOBSERVER]
7.12 For each fully active Document in docs, update the rendering or user interface of that Document and its browsing context to reflect the current state.

8.If this is a worker event loop (i.e. one running for a WorkerGlobalScope), but there are no tasks in the event loop"s task queues and the WorkerGlobalScope object"s closing flag is true, then destroy the event loop, aborting these steps, resuming the run a worker steps described in the Web workers section below.
9.Return to the first step of the event loop.

解釋一下:第一步,從多個task queue中的一個queue里,挑出一個最老的task。(因為有多個task queue的存在,使得瀏覽器可以完成我們前面說的,優(yōu)先、高頻率的執(zhí)行某些task queue中的任務(wù),比如UI的task queue)。
然后2到5步,執(zhí)行這個task。
第六步, Perform a microtask checkpoint. ,這里會執(zhí)行完microtask queue中的所有的microtask,如果microtask執(zhí)行過程中又添加了microtask,那么仍然會執(zhí)行新添加的microtask,當(dāng)然,這個機制好像有限制,一輪microtask的執(zhí)行總量似乎有限制(1000?),數(shù)量太多就執(zhí)行一部分留下的以后再執(zhí)行?這里我不太確定。

第七步,Update the rendering:
7.2到7.4,當(dāng)前輪次的event loop中關(guān)聯(lián)到的document對象會保持某些特定順序,這些document對象都會執(zhí)行需要執(zhí)行UI render的,但是并不是所有關(guān)聯(lián)到的document都需要更新UI,瀏覽器會判斷這個document是否會從UI Render中獲益,因為瀏覽器只需要保持60Hz的刷新率即可,而每輪event loop都是非常快的,所以沒必要每個document都Render UI。
7.5和7.6 run the resize steps/run the scroll steps不是說去執(zhí)行resize和scroll。每次我們scoll的時候視口或者dom就已經(jīng)立即scroll了,并把document或者dom加入到 pending scroll event targets中,而run the scroll steps具體做的則是遍歷這些target,在target上觸發(fā)scroll事件。run the resize steps也是相似的,這個步驟是觸發(fā)resize事件。
7.8和7.9 后續(xù)的media query, run CSS animations and send events等等也是相似的,都是觸發(fā)事件,第10步和第11步則是執(zhí)行我們熟悉的requestAnimationFrame回調(diào)和IntersectionObserver回調(diào)(第十步還是挺關(guān)鍵的,raf就是在這執(zhí)行的!)。
7.12 渲染UI,關(guān)鍵就在這了。

第九步 繼續(xù)執(zhí)行event loop,又去執(zhí)行task,microtasks和UI render。

更新:找到一張圖,不過著重說明的是整個event loop,沒有細說UI render。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/81966.html

相關(guān)文章

  • event loop 與 vue

    摘要:但是導(dǎo)致了很明顯的性能問題。上述兩個例子其實是在這個中找到的,第一個使用的版本是,這個版本的實現(xiàn)是采用了,而后因為的里的有,于是尤雨溪更改了實現(xiàn),換成了,也就是后一個所使用的。后來尤雨溪了解到是將回調(diào)放入的隊列。 結(jié)論 對于event loop 可以抽象成一段簡單的代碼表示 for (macroTask of macroTaskQueue) { // 1. Handle cur...

    springDevBird 評論0 收藏0
  • event loop 與 vue

    摘要:但是導(dǎo)致了很明顯的性能問題。上述兩個例子其實是在這個中找到的,第一個使用的版本是,這個版本的實現(xiàn)是采用了,而后因為的里的有,于是尤雨溪更改了實現(xiàn),換成了,也就是后一個所使用的。后來尤雨溪了解到是將回調(diào)放入的隊列。 結(jié)論 對于event loop 可以抽象成一段簡單的代碼表示 for (macroTask of macroTaskQueue) { // 1. Handle cur...

    Barry_Ng 評論0 收藏0
  • Vue源碼Vue中DOM的異步更新策略以及nextTick機制

    摘要:本篇文章主要是對中的異步更新策略和機制的解析,需要讀者有一定的使用經(jīng)驗并且熟悉掌握事件循環(huán)模型。這個結(jié)果足以說明中的更新并非同步。二是把回調(diào)函數(shù)放入一個隊列,等待適當(dāng)?shù)臅r機執(zhí)行。通過的主動來觸發(fā)的事件,進而把回調(diào)函數(shù)作為參與事件循環(huán)。 本篇文章主要是對Vue中的DOM異步更新策略和nextTick機制的解析,需要讀者有一定的Vue使用經(jīng)驗并且熟悉掌握JavaScript事件循環(huán)模型。 ...

    selfimpr 評論0 收藏0
  • 淺析 Vue 2.6 中的 nextTick 方法

    摘要:核心的異步延遲函數(shù),用于異步延遲調(diào)用函數(shù)優(yōu)先使用原生原本支持更廣,但在的中,觸摸事件處理程序中觸發(fā)會產(chǎn)生嚴重錯誤的,回調(diào)被推入隊列但是隊列可能不會如期執(zhí)行。 淺析 Vue 2.6 中的 nextTick 方法。 事件循環(huán) JS 的 事件循環(huán) 和 任務(wù)隊列 其實是理解 nextTick 概念的關(guān)鍵。這個網(wǎng)上其實有很多優(yōu)質(zhì)的文章做了詳細介紹,我就簡單過過了。 以下內(nèi)容適用于瀏覽器端 JS,...

    fobnn 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<