欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Vue采用異步渲染的原理分析

 更新時間:2023年06月02日 11:23:19   作者:WindrunnerMax  
對于Vue為何采用異步渲染,簡單來說就是為了提升性能,因?yàn)椴徊捎卯惒礁?,在每次更新?shù)據(jù)都會對當(dāng)前組件進(jìn)行重新渲染,為了性能考慮,Vue會在本輪數(shù)據(jù)更新后,再去異步更新視圖,本文主要通過幾個實(shí)例給大家介紹一下Vue為何采用異步渲染,需要的朋友可以參考下

Vue為何采用異步渲染

Vue在更新DOM時是異步執(zhí)行的,只要偵聽到數(shù)據(jù)變化,Vue將開啟一個隊(duì)列,并緩沖在同一事件循環(huán)中發(fā)生的所有數(shù)據(jù)變更,如果同一個watcher被多次觸發(fā),只會被推入到隊(duì)列中一次,這種在緩沖時去除重復(fù)數(shù)據(jù)對于避免不必要的計(jì)算和DOM操作是非常重要的,然后,在下一個的事件循環(huán)tick中,Vue刷新隊(duì)列并執(zhí)行實(shí)際(已去重的)工作,Vue在內(nèi)部對異步隊(duì)列嘗試使用原生的Promise.then、MutationObserversetImmediate,如果執(zhí)行環(huán)境不支持,則會采用setTimeout(fn, 0)代替。

描述

對于Vue為何采用異步渲染,簡單來說就是為了提升性能,因?yàn)椴徊捎卯惒礁拢诿看胃聰?shù)據(jù)都會對當(dāng)前組件進(jìn)行重新渲染,為了性能考慮,Vue會在本輪數(shù)據(jù)更新后,再去異步更新視圖,舉個例子,讓我們在一個方法內(nèi)重復(fù)更新一個值。

this.msg = 1;
this.msg = 2;
this.msg = 3;

事實(shí)上,我們真正想要的其實(shí)只是最后一次更新而已,也就是說前三次DOM更新都是可以省略的,我們只需要等所有狀態(tài)都修改好了之后再進(jìn)行渲染就可以減少一些性能損耗。
對于渲染方面的問題是很明確的,最終只渲染一次肯定比修改之后即渲染所耗費(fèi)的性能少,在這里我們還需要考慮一下異步更新隊(duì)列的相關(guān)問題,假設(shè)我們現(xiàn)在是進(jìn)行了相關(guān)處理使得每次更新數(shù)據(jù)只進(jìn)行一次真實(shí)DOM渲染,來讓我們考慮異步更新隊(duì)列的性能優(yōu)化。
假設(shè)這里是同步更新隊(duì)列,this.msg=1,大致會發(fā)生這些事: msg值更新 -> 觸發(fā)setter -> 觸發(fā)Watcherupdate -> 重新調(diào)用 render -> 生成新的vdom -> dom-diff -> dom更新,這里的dom更新并不是渲染(即布局、繪制、合成等一系列步驟),而是更新內(nèi)存中的DOM樹結(jié)構(gòu),之后再運(yùn)行this.msg=2,再重復(fù)上述步驟,之后的第3次更新同樣會觸發(fā)相同的流程,等開始渲染的時候,最新的DOM樹中確實(shí)只會存在更新完成3,從這里來看,前2次對msg的操作以及Vue內(nèi)部對它的處理都是無用的操作,可以進(jìn)行優(yōu)化處理。
如果是異步更新隊(duì)列,會是下面的情況,運(yùn)行this.msg=1,并不是立即進(jìn)行上面的流程,而是將對msg有依賴的Watcher都保存在隊(duì)列中,該隊(duì)列可能這樣[Watcher1, Watcher2...],當(dāng)運(yùn)行this.msg=2后,同樣是將對msg有依賴的Watcher保存到隊(duì)列中,Vue內(nèi)部會做去重判斷,這次操作后,可以認(rèn)為隊(duì)列數(shù)據(jù)沒有發(fā)生變化,第3次更新也是上面的過程,當(dāng)然,你不可能只對msg有操作,你可能對該組件中的另一個屬性也有操作,比如this.otherMsg=othermessage,同樣會把對otherMsg有依賴的Watcher添加到異步更新隊(duì)列中,因?yàn)橛兄貜?fù)判斷操作,這個Watcher也只會在隊(duì)列中存在一次,本次異步任務(wù)執(zhí)行結(jié)束后,會進(jìn)入下一個任務(wù)執(zhí)行流程,其實(shí)就是遍歷異步更新隊(duì)列中的每一個Watcher,觸發(fā)其update,然后進(jìn)行重新調(diào)用render -> new vdom -> dom-diff -> dom更新等流程,但是這種方式和同步更新隊(duì)列相比,不管操作多少次msg, Vue在內(nèi)部只會進(jìn)行一次重新調(diào)用真實(shí)更新流程,所以,對于異步更新隊(duì)列不是節(jié)省了渲染成本,而是節(jié)省了Vue內(nèi)部計(jì)算及DOM樹操作的成本,不管采用哪種方式,渲染確實(shí)只有一次。
此外,組件內(nèi)部實(shí)際使用VirtualDOM進(jìn)行渲染,也就是說,組件內(nèi)部其實(shí)是不關(guān)心哪個狀態(tài)發(fā)生了變化,它只需要計(jì)算一次就可以得知哪些節(jié)點(diǎn)需要更新,也就是說,如果更改了N個狀態(tài),其實(shí)只需要發(fā)送一個信號就可以將DOM更新到最新,如果我們更新多個值。

this.msg = 1;
this.age = 2;
this.name = 3;

此處我們分三次修改了三種狀態(tài),但其實(shí)Vue只會渲染一次,因?yàn)?code>VIrtualDOM只需要一次就可以將整個組件的DOM更新到最新,它根本不會關(guān)心這個更新的信號到底是從哪個具體的狀態(tài)發(fā)出來的。
而為了達(dá)到這個目的,我們需要將渲染操作推遲到所有的狀態(tài)都修改完成,為了做到這一點(diǎn)只需要將渲染操作推遲到本輪事件循環(huán)的最后或者下一輪事件循環(huán),也就是說,只需要在本輪事件循環(huán)的最后,等前面更新狀態(tài)的語句都執(zhí)行完之后,執(zhí)行一次渲染操作,它就可以無視前面各種更新狀態(tài)的語法,無論前面寫了多少條更新狀態(tài)的語句,只在最后渲染一次就可以了。
將渲染推遲到本輪事件循環(huán)的最后執(zhí)行渲染的時機(jī)會比推遲到下一輪快很多,所以Vue優(yōu)先將渲染操作推遲到本輪事件循環(huán)的最后,如果執(zhí)行環(huán)境不支持會降級到下一輪,Vue的變化偵測機(jī)制(setter)決定了它必然會在每次狀態(tài)發(fā)生變化時都會發(fā)出渲染的信號,但Vue會在收到信號之后檢查隊(duì)列中是否已經(jīng)存在這個任務(wù),保證隊(duì)列中不會有重復(fù),如果隊(duì)列中不存在則將渲染操作添加到隊(duì)列中,之后通過異步的方式延遲執(zhí)行隊(duì)列中的所有渲染的操作并清空隊(duì)列,當(dāng)同一輪事件循環(huán)中反復(fù)修改狀態(tài)時,并不會反復(fù)向隊(duì)列中添加相同的渲染操作,所以我們在使用Vue時,修改狀態(tài)后更新DOM都是異步的。
當(dāng)數(shù)據(jù)變化后會調(diào)用notify方法,將watcher遍歷,調(diào)用update方法通知watcher進(jìn)行更新,這時候watcher并不會立即去執(zhí)行,在update中會調(diào)用queueWatcher方法將watcher放到了一個隊(duì)列里,在queueWatcher會根據(jù)watcher的進(jìn)行去重,若多個屬性依賴一個watcher,則如果隊(duì)列中沒有該watcher就會將該watcher添加到隊(duì)列中,然后便會在$nextTick方法的執(zhí)行隊(duì)列中加入一個flushSchedulerQueue方法(這個方法將會觸發(fā)在緩沖隊(duì)列的所有回調(diào)的執(zhí)行),然后將$nextTick方法的回調(diào)加入$nextTick方法中維護(hù)的執(zhí)行隊(duì)列,flushSchedulerQueue中開始會觸發(fā)一個before的方法,其實(shí)就是beforeUpdate,然后watcher.run()才開始真正執(zhí)行watcher,執(zhí)行完頁面就渲染完成,更新完成后會調(diào)用updated鉤子。

$nextTick

在上文中談到了對于Vue為何采用異步渲染,假如此時我們有一個需求,需要在頁面渲染完成后取得頁面的DOM元素,而由于渲染是異步的,我們不能直接在定義的方法中同步取得這個值的,于是就有了vm.$nextTick方法,Vue$nextTick方法將回調(diào)延遲到下次DOM更新循環(huán)之后執(zhí)行,也就是在下次DOM更新循環(huán)結(jié)束之后執(zhí)行延遲回調(diào),在修改數(shù)據(jù)之后立即使用這個方法,能夠獲取更新后的DOM。簡單來說就是當(dāng)數(shù)據(jù)更新時,在DOM中渲染完成后,執(zhí)行回調(diào)函數(shù)。
通過一個簡單的例子來演示$nextTick方法的作用,首先需要知道Vue在更新DOM時是異步執(zhí)行的,也就是說在更新數(shù)據(jù)時其不會阻塞代碼的執(zhí)行,直到執(zhí)行棧中代碼執(zhí)行結(jié)束之后,才開始執(zhí)行異步任務(wù)隊(duì)列的代碼,所以在數(shù)據(jù)更新時,組件不會立即渲染,此時在獲取到DOM結(jié)構(gòu)后取得的值依然是舊的值,而在$nextTick方法中設(shè)定的回調(diào)函數(shù)會在組件渲染完成之后執(zhí)行,取得DOM結(jié)構(gòu)后取得的值便是新的值。

<!DOCTYPE html>
<html>
<head>
    <title>Vue</title>
</head>
<body>
    <div id="app"></div>
</body>
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.js"></script>
<script type="text/javascript">
    var vm = new Vue({
        el: '#app',
        data: {
            msg: 'Vue'
        },
        template:`
            <div>
                <div ref="msgElement">{{msg}}</div>
                <button @click="updateMsg">updateMsg</button>
            </div>
        `,
        methods:{
            updateMsg: function(){
                this.msg = "Update";
                console.log("DOM未更新:", this.$refs.msgElement.innerHTML)
                this.$nextTick(() => {
                    console.log("DOM已更新:", this.$refs.msgElement.innerHTML)
                })
            }
        },
    })
</script>
</html>

異步機(jī)制

官方文檔中說明,Vue在更新DOM時是異步執(zhí)行的,只要偵聽到數(shù)據(jù)變化,Vue將開啟一個隊(duì)列,并緩沖在同一事件循環(huán)中發(fā)生的所有數(shù)據(jù)變更,如果同一個watcher被多次觸發(fā),只會被推入到隊(duì)列中一次。這種在緩沖時去除重復(fù)數(shù)據(jù)對于避免不必要的計(jì)算和DOM操作是非常重要的。然后,在下一個的事件循環(huán)tick中,Vue刷新隊(duì)列并執(zhí)行實(shí)際工作。Vue在內(nèi)部對異步隊(duì)列嘗試使用原生的Promise.then、MutationObserversetImmediate,如果執(zhí)行環(huán)境不支持,則會采用 setTimeout(fn, 0)代替。
Js是單線程的,其引入了同步阻塞與異步非阻塞的執(zhí)行模式,在Js異步模式中維護(hù)了一個Event LoopEvent Loop是一個執(zhí)行模型,在不同的地方有不同的實(shí)現(xiàn),瀏覽器和NodeJS基于不同的技術(shù)實(shí)現(xiàn)了各自的Event Loop。瀏覽器的Event Loop是在HTML5的規(guī)范中明確定義,NodeJSEvent Loop是基于libuv實(shí)現(xiàn)的。
在瀏覽器中的Event Loop由執(zhí)行棧Execution Stack、后臺線程Background Threads、宏隊(duì)列Macrotask Queue、微隊(duì)列Microtask Queue組成。

  • 執(zhí)行棧就是在主線程執(zhí)行同步任務(wù)的數(shù)據(jù)結(jié)構(gòu),函數(shù)調(diào)用形成了一個由若干幀組成的棧。
  • 后臺線程就是瀏覽器實(shí)現(xiàn)對于setTimeout、setIntervalXMLHttpRequest等等的執(zhí)行線程。
  • 宏隊(duì)列,一些異步任務(wù)的回調(diào)會依次進(jìn)入宏隊(duì)列,等待后續(xù)被調(diào)用,包括setTimeoutsetInterval、setImmediate(Node)requestAnimationFrame、UI rendering、I/O等操作。
  • 微隊(duì)列,另一些異步任務(wù)的回調(diào)會依次進(jìn)入微隊(duì)列,等待后續(xù)調(diào)用,包括Promise、process.nextTick(Node)、Object.observeMutationObserver等操作。

當(dāng)Js執(zhí)行時,進(jìn)行如下流程:

  • 首先將執(zhí)行棧中代碼同步執(zhí)行,將這些代碼中異步任務(wù)加入后臺線程中。
  • 執(zhí)行棧中的同步代碼執(zhí)行完畢后,執(zhí)行棧清空,并開始掃描微隊(duì)列。
  • 取出微隊(duì)列隊(duì)首任務(wù),放入執(zhí)行棧中執(zhí)行,此時微隊(duì)列是進(jìn)行了出隊(duì)操作。
  • 當(dāng)執(zhí)行棧執(zhí)行完成后,繼續(xù)出隊(duì)微隊(duì)列任務(wù)并執(zhí)行,直到微隊(duì)列任務(wù)全部執(zhí)行完畢。
  • 最后一個微隊(duì)列任務(wù)出隊(duì)并進(jìn)入執(zhí)行棧后微隊(duì)列中任務(wù)為空,當(dāng)執(zhí)行棧任務(wù)完成后,開始掃面微隊(duì)列為空,繼續(xù)掃描宏隊(duì)列任務(wù),宏隊(duì)列出隊(duì),放入執(zhí)行棧中執(zhí)行,執(zhí)行完畢后繼續(xù)掃描微隊(duì)列為空則掃描宏隊(duì)列,出隊(duì)執(zhí)行。
  • 不斷往復(fù)...。

實(shí)例

// Step 1
console.log(1);
// Step 2
setTimeout(() => {
  console.log(2);
  Promise.resolve().then(() => {
    console.log(3);
  });
}, 0);
// Step 3
new Promise((resolve, reject) => {
  console.log(4);
  resolve();
}).then(() => {
  console.log(5);
})
// Step 4
setTimeout(() => {
  console.log(6);
}, 0);
// Step 5
console.log(7);
// Step N
// ...
// Result
/*
  1
  4
  7
  5
  2
  3
  6
*/

Step 1

// 執(zhí)行棧 console
// 微隊(duì)列 []
// 宏隊(duì)列 []
console.log(1); // 1

Step 2

// 執(zhí)行棧 setTimeout
// 微隊(duì)列 []
// 宏隊(duì)列 [setTimeout1]
setTimeout(() => {
  console.log(2);
  Promise.resolve().then(() => {
    console.log(3);
  });
}, 0);

Step 3

// 執(zhí)行棧 Promise
// 微隊(duì)列 [then1]
// 宏隊(duì)列 [setTimeout1]
new Promise((resolve, reject) => {
  console.log(4); // 4 // Promise是個函數(shù)對象,此處是同步執(zhí)行的 // 執(zhí)行棧 Promise console
  resolve();
}).then(() => {
  console.log(5);
})

Step 4

// 執(zhí)行棧 setTimeout
// 微隊(duì)列 [then1]
// 宏隊(duì)列 [setTimeout1 setTimeout2]
setTimeout(() => {
  console.log(6);
}, 0);

Step 5

// 執(zhí)行棧 console
// 微隊(duì)列 [then1]
// 宏隊(duì)列 [setTimeout1 setTimeout2]
console.log(7); // 7

Step 6

// 執(zhí)行棧 then1
// 微隊(duì)列 []
// 宏隊(duì)列 [setTimeout1 setTimeout2]
console.log(5); // 5

Step 7

// 執(zhí)行棧 setTimeout1
// 微隊(duì)列 [then2]
// 宏隊(duì)列 [setTimeout2]
console.log(2); // 2
Promise.resolve().then(() => {
    console.log(3);
});

Step 8

// 執(zhí)行棧 then2
// 微隊(duì)列 []
// 宏隊(duì)列 [setTimeout2]
console.log(3); // 3

Step 9

// 執(zhí)行棧 setTimeout2
// 微隊(duì)列 []
// 宏隊(duì)列 []
console.log(6); // 6

分析

在了解異步任務(wù)的執(zhí)行隊(duì)列后,回到中$nextTick方法,當(dāng)用戶數(shù)據(jù)更新時,Vue將會維護(hù)一個緩沖隊(duì)列,對于所有的更新數(shù)據(jù)將要進(jìn)行的組件渲染與DOM操作進(jìn)行一定的策略處理后加入緩沖隊(duì)列,然后便會在$nextTick方法的執(zhí)行隊(duì)列中加入一個flushSchedulerQueue方法(這個方法將會觸發(fā)在緩沖隊(duì)列的所有回調(diào)的執(zhí)行),然后將$nextTick方法的回調(diào)加入$nextTick方法中維護(hù)的執(zhí)行隊(duì)列,在異步掛載的執(zhí)行隊(duì)列觸發(fā)時就會首先會首先執(zhí)行flushSchedulerQueue方法來處理DOM渲染的任務(wù),然后再去執(zhí)行$nextTick方法構(gòu)建的任務(wù),這樣就可以實(shí)現(xiàn)在$nextTick方法中取得已渲染完成的DOM結(jié)構(gòu)。在測試的過程中發(fā)現(xiàn)了一個很有意思的現(xiàn)象,在上述例子中的加入兩個按鈕,在點(diǎn)擊updateMsg按鈕的結(jié)果是3 2 1,點(diǎn)擊updateMsgTest按鈕的運(yùn)行結(jié)果是2 3 1。

<!DOCTYPE html>
<html>
<head>
    <title>Vue</title>
</head>
<body>
    <div id="app"></div>
</body>
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.js"></script>
<script type="text/javascript">
    var vm = new Vue({
        el: '#app',
        data: {
            msg: 'Vue'
        },
        template:`
            <div>
                <div ref="msgElement">{{msg}}</div>
                <button @click="updateMsg">updateMsg</button>
                <button @click="updateMsgTest">updateMsgTest</button>
            </div>
        `,
        methods:{
            updateMsg: function(){
                this.msg = "Update";
                setTimeout(() => console.log(1))
                Promise.resolve().then(() => console.log(2))
                this.$nextTick(() => {
                    console.log(3)
                })
            },
            updateMsgTest: function(){
                setTimeout(() => console.log(1))
                Promise.resolve().then(() => console.log(2))
                this.$nextTick(() => {
                    console.log(3)
                })
            }
        },
    })
</script>
</html>

這里假設(shè)運(yùn)行環(huán)境中Promise對象是完全支持的,那么使用setTimeout是宏隊(duì)列在最后執(zhí)行這個是沒有異議的,但是使用$nextTick方法以及自行定義的Promise實(shí)例是有執(zhí)行順序的問題的,雖然都是微隊(duì)列任務(wù),但是在Vue中具體實(shí)現(xiàn)的原因?qū)е铝藞?zhí)行順序可能會有所不同,首先直接看一下$nextTick方法的源碼,關(guān)鍵地方添加了注釋,請注意這是Vue2.4.2版本的源碼,在后期$nextTick方法可能有所變更。

/**
 * Defer a task to execute it asynchronously.
 */
var nextTick = (function () {
  // 閉包 內(nèi)部變量
  var callbacks = []; // 執(zhí)行隊(duì)列
  var pending = false; // 標(biāo)識,用以判斷在某個事件循環(huán)中是否為第一次加入,第一次加入的時候才觸發(fā)異步執(zhí)行的隊(duì)列掛載
  var timerFunc; // 以何種方法執(zhí)行掛載異步執(zhí)行隊(duì)列,這里假設(shè)Promise是完全支持的
  function nextTickHandler () { // 異步掛載的執(zhí)行任務(wù),觸發(fā)時就已經(jīng)正式準(zhǔn)備開始執(zhí)行異步任務(wù)了
    pending = false; // 標(biāo)識置false
    var copies = callbacks.slice(0); // 創(chuàng)建副本
    callbacks.length = 0; // 執(zhí)行隊(duì)列置空
    for (var i = 0; i < copies.length; i++) {
      copies[i](); // 執(zhí)行
    }
  }
  // the nextTick behavior leverages the microtask queue, which can be accessed
  // via either native Promise.then or MutationObserver.
  // MutationObserver has wider support, however it is seriously bugged in
  // UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It
  // completely stops working after triggering a few times... so, if native
  // Promise is available, we will use it:
  /* istanbul ignore if */
  if (typeof Promise !== 'undefined' && isNative(Promise)) {
    var p = Promise.resolve();
    var logError = function (err) { console.error(err); };
    timerFunc = function () {
      p.then(nextTickHandler).catch(logError); // 掛載異步任務(wù)隊(duì)列
      // 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
    var counter = 1;
    var observer = new MutationObserver(nextTickHandler);
    var textNode = document.createTextNode(String(counter));
    observer.observe(textNode, {
      characterData: true
    });
    timerFunc = function () {
      counter = (counter + 1) % 2;
      textNode.data = String(counter);
    };
  } else {
    // fallback to setTimeout
    /* istanbul ignore next */
    timerFunc = function () {
      setTimeout(nextTickHandler, 0);
    };
  }
  return function queueNextTick (cb, ctx) { // nextTick方法真正導(dǎo)出的方法
    var _resolve;
    callbacks.push(function () { // 添加到執(zhí)行隊(duì)列中 并加入異常處理
      if (cb) {
        try {
          cb.call(ctx);
        } catch (e) {
          handleError(e, ctx, 'nextTick');
        }
      } else if (_resolve) {
        _resolve(ctx);
      }
    });
    //判斷在當(dāng)前事件循環(huán)中是否為第一次加入,若是第一次加入則置標(biāo)識為true并執(zhí)行timerFunc函數(shù)用以掛載執(zhí)行隊(duì)列到Promise
    // 這個標(biāo)識在執(zhí)行隊(duì)列中的任務(wù)將要執(zhí)行時便置為false并創(chuàng)建執(zhí)行隊(duì)列的副本去運(yùn)行執(zhí)行隊(duì)列中的任務(wù),參見nextTickHandler函數(shù)的實(shí)現(xiàn)
    // 在當(dāng)前事件循環(huán)中置標(biāo)識true并掛載,然后再次調(diào)用nextTick方法時只是將任務(wù)加入到執(zhí)行隊(duì)列中,直到掛載的異步任務(wù)觸發(fā),便置標(biāo)識為false然后執(zhí)行任務(wù),再次調(diào)用nextTick方法時就是同樣的執(zhí)行方式然后不斷如此往復(fù)
    if (!pending) { 
      pending = true;
      timerFunc();
    }
    if (!cb && typeof Promise !== 'undefined') {
      return new Promise(function (resolve, reject) {
        _resolve = resolve;
      })
    }
  }
})();

回到剛才提出的問題上,在更新DOM操作時會先觸發(fā)$nextTick方法的回調(diào),解決這個問題的關(guān)鍵在于誰先將異步任務(wù)掛載到Promise對象上。
首先對有數(shù)據(jù)更新的updateMsg按鈕觸發(fā)的方法進(jìn)行debug,斷點(diǎn)設(shè)置在Vue.js715行,版本為2.4.2,在查看調(diào)用棧以及傳入的參數(shù)時可以觀察到第一次執(zhí)行$nextTick方法的其實(shí)是由于數(shù)據(jù)更新而調(diào)用的nextTick(flushSchedulerQueue);語句,也就是說在執(zhí)行this.msg = "Update";的時候就已經(jīng)觸發(fā)了第一次的$nextTick方法,此時在$nextTick方法中的任務(wù)隊(duì)列會首先將flushSchedulerQueue方法加入隊(duì)列并掛載$nextTick方法的執(zhí)行隊(duì)列到Promise對象上,然后才是自行自定義的Promise.resolve().then(() => console.log(2))語句的掛載,當(dāng)執(zhí)行微任務(wù)隊(duì)列中的任務(wù)時,首先會執(zhí)行第一個掛載到Promise的任務(wù),此時這個任務(wù)是運(yùn)行執(zhí)行隊(duì)列,這個隊(duì)列中有兩個方法,首先會運(yùn)行flushSchedulerQueue方法去觸發(fā)組件的DOM渲染操作,然后再執(zhí)行console.log(3),然后執(zhí)行第二個微隊(duì)列的任務(wù)也就是() => console.log(2),此時微任務(wù)隊(duì)列清空,然后再去宏任務(wù)隊(duì)列執(zhí)行console.log(1)。
接下來對于沒有數(shù)據(jù)更新的updateMsgTest按鈕觸發(fā)的方法進(jìn)行debug,斷點(diǎn)設(shè)置在同樣的位置,此時沒有數(shù)據(jù)更新,那么第一次觸發(fā)$nextTick方法的是自行定義的回調(diào)函數(shù),那么此時$nextTick方法的執(zhí)行隊(duì)列才會被掛載到Promise對象上,很顯然在此之前自行定義的輸出2Promise回調(diào)已經(jīng)被掛載,那么對于這個按鈕綁定的方法的執(zhí)行流程便是首先執(zhí)行console.log(2),然后執(zhí)行$nextTick方法閉包的執(zhí)行隊(duì)列,此時執(zhí)行隊(duì)列中只有一個回調(diào)函數(shù)console.log(3),此時微任務(wù)隊(duì)列清空,然后再去宏任務(wù)隊(duì)列執(zhí)行console.log(1)。
簡單來說就是誰先掛載Promise對象的問題,在調(diào)用$nextTick方法時就會將其閉包內(nèi)部維護(hù)的執(zhí)行隊(duì)列掛載到Promise對象,在數(shù)據(jù)更新時Vue內(nèi)部首先就會執(zhí)行$nextTick方法,之后便將執(zhí)行隊(duì)列掛載到了Promise對象上,其實(shí)在明白JsEvent Loop模型后,將數(shù)據(jù)更新也看做一個$nextTick方法的調(diào)用,并且明白$nextTick方法會一次性執(zhí)行所有推入的回調(diào),就可以明白其執(zhí)行順序的問題了,下面是一個關(guān)于$nextTick方法的最小化的DEMO。

var nextTick = (function(){
    var pending = false;
    const callback = [];
    var p = Promise.resolve();
    var handler = function(){
        pending = true;
        callback.forEach(fn => fn());
    }
    var timerFunc = function(){
        p.then(handler);
    }
    return function queueNextTick(fn){
        callback.push(() => fn());
        if(!pending){
            pending = true;
            timerFunc();
        }
    }
})();
(function(){
    nextTick(() => console.log("觸發(fā)DOM渲染隊(duì)列的方法")); // 注釋 / 取消注釋 來查看效果
    setTimeout(() => console.log(1))
    Promise.resolve().then(() => console.log(2))
    nextTick(() => {
        console.log(3)
    })
})();

以上就是Vue采用異步渲染的原理分析的詳細(xì)內(nèi)容,更多關(guān)于Vue 異步渲染的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • vue動態(tài)添加store、路由和國際化配置方式

    vue動態(tài)添加store、路由和國際化配置方式

    這篇文章主要介紹了vue動態(tài)添加store、路由和國際化配置方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-03-03
  • vue計(jì)算屬性computed的使用方法示例

    vue計(jì)算屬性computed的使用方法示例

    這篇文章主要介紹了vue計(jì)算屬性computed的使用方法,結(jié)合實(shí)例形式分析了vue計(jì)算屬性computed的基本用法及相關(guān)操作注意事項(xiàng),需要的朋友可以參考下
    2019-03-03
  • 淺談vue加載優(yōu)化策略

    淺談vue加載優(yōu)化策略

    這篇文章主要介紹了淺談vue加載優(yōu)化策略,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2019-03-03
  • vue中封裝axios并實(shí)現(xiàn)api接口的統(tǒng)一管理

    vue中封裝axios并實(shí)現(xiàn)api接口的統(tǒng)一管理

    這篇文章主要介紹了vue中封裝axios并實(shí)現(xiàn)api接口的統(tǒng)一管理的方法,幫助大家更好的理解和使用vue,感興趣的朋友可以了解下
    2020-12-12
  • vue的路由映射問題及解決方案

    vue的路由映射問題及解決方案

    這篇文章主要介紹了vue的路由映射問題及解決方案,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2019-10-10
  • Vue 事件處理操作實(shí)例詳解

    Vue 事件處理操作實(shí)例詳解

    這篇文章主要介紹了Vue 事件處理操作,結(jié)合實(shí)例形式較為詳細(xì)的分析了vue.js事件處理相關(guān)的事件監(jiān)聽、處理、修飾符等相關(guān)概念、用法及操作注意事項(xiàng),需要的朋友可以參考下
    2019-03-03
  • Vue3.0組件通信mitt源碼ts實(shí)例解析

    Vue3.0組件通信mitt源碼ts實(shí)例解析

    這篇文章主要為大家介紹了Vue3.0組件通信mitt源碼ts實(shí)例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-11-11
  • vue使用axios實(shí)現(xiàn)excel文件下載的功能

    vue使用axios實(shí)現(xiàn)excel文件下載的功能

    這篇文章主要介紹了vue中使用axios實(shí)現(xiàn)excel文件下載的功能,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-07-07
  • vue移動端使用canvas簽名的實(shí)現(xiàn)

    vue移動端使用canvas簽名的實(shí)現(xiàn)

    這篇文章主要介紹了vue移動端使用canvas簽名的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-01-01
  • Vue如何實(shí)現(xiàn)iframe的上一步、下一步操作

    Vue如何實(shí)現(xiàn)iframe的上一步、下一步操作

    這篇文章主要介紹了Vue如何實(shí)現(xiàn)iframe的上一步、下一步操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-06-06

最新評論