日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区

您的位置:首頁技術(shù)文章
文章詳情頁

深入解讀VUE中的異步渲染的實(shí)現(xiàn)

瀏覽:15日期:2023-01-12 09:31:18

接下來在本文里一起看看當(dāng)數(shù)據(jù)變化時,從源碼層面逐步分析一下觸發(fā)頁面的響應(yīng)動作之后,如何做渲染到頁面上,展示到用戶層面的。

同時也會了解在Vue中的異步方法NextTick的源碼實(shí)現(xiàn),看一看NextTick方法與瀏覽器的異步API有何聯(lián)系。

注意,本文涉及的Vue源碼版本為2.6.11。

什么是異步渲染?

這個問題應(yīng)該先要做一個前提補(bǔ)充,當(dāng)數(shù)據(jù)在同步變化的時候,頁面訂閱的響應(yīng)操作為什么不會與數(shù)據(jù)變化完全對應(yīng),而是在所有的數(shù)據(jù)變化操作做完之后,頁面才會得到響應(yīng),完成頁面渲染。

從一個例子體驗(yàn)一下異步渲染機(jī)制。

import Vue from ’Vue’new Vue({ el: ’#app’, template: ’<div>{{val}}</div>’, data () { return { val: ’init’ } }, mounted () { this.val = ’我是第一次頁面渲染’ // debugger this.val = ’我是第二次頁面渲染’ const st = Date.now() while(Date.now() - st < 3000) {} }})

上面這一段代碼中,在mounted里給val屬性進(jìn)行了兩次賦值,如果頁面渲染與數(shù)據(jù)的變化完全同步的話,頁面應(yīng)該是在mounted里有兩次渲染。

而由于Vue內(nèi)部的渲染機(jī)制,實(shí)際上頁面只會渲染一次,把第一次的賦值所帶來的的響應(yīng)與第二次的賦值所帶來的的響應(yīng)進(jìn)行一次合并,將最終的val只做一次頁面渲染。

而且頁面是在執(zhí)行所有的同步代碼執(zhí)行完后才能得到渲染,在上述例子里的while阻塞代碼之后,頁面才會得到渲染,就像在熟悉的setTimeout里的回調(diào)函數(shù)的執(zhí)行一樣,這就是的異步渲染。

熟悉React的同學(xué),應(yīng)該很快能想到多次執(zhí)行setState函數(shù)時,頁面render的渲染觸發(fā),實(shí)際上與上面所說的Vue的異步渲染有異曲同工之妙。

Vue為什么要異步渲染?

我們可以從用戶和性能兩個角度來探討這個問題。

從用戶體驗(yàn)角度,從上面例子里便也可以看出,實(shí)際上我們的頁面只需要展示第二次的值變化,第一次只是一個中間值,如果渲染后給用戶展示,頁面會有閃爍效果,反而會造成不好的用戶體驗(yàn)。

從性能角度,例子里最終的需要展示的數(shù)據(jù)其實(shí)就是第二次給val賦的值,如果第一次賦值也需要頁面渲染則意味著在第二次最終的結(jié)果渲染之前頁面還需要渲染一次無用的渲染,無疑增加了性能的消耗。

對于瀏覽器來說,在數(shù)據(jù)變化下,無論是引起的重繪渲染還是重排渲染,都有可能會在性能消耗之下造成低效的頁面性能,甚至造成加載卡頓問題。

異步渲染和熟悉的節(jié)流函數(shù)最終目的是一致的,將多次數(shù)據(jù)變化所引起的響應(yīng)變化收集后合并成一次頁面渲染,從而更合理的利用機(jī)器資源,提升性能與用戶體驗(yàn)。

Vue中如何實(shí)現(xiàn)異步渲染?

先總結(jié)一下原理,在Vue中異步渲染實(shí)際在數(shù)據(jù)每次變化時,將其所要引起頁面變化的部分都放到一個異步API的回調(diào)函數(shù)里,直到同步代碼執(zhí)行完之后,異步回調(diào)開始執(zhí)行,最終將同步代碼里所有的需要渲染變化的部分合并起來,最終執(zhí)行一次渲染操作。

拿上面例子來說,當(dāng)val第一次賦值時,頁面會渲染出對應(yīng)的文字,但是實(shí)際這個渲染變化會暫存,val第二次賦值時,再次暫存將要引起的變化,這些變化操作會被丟到異步API,Promise.then的回調(diào)函數(shù)中,等到所有同步代碼執(zhí)行完后,then函數(shù)的回調(diào)函數(shù)得到執(zhí)行,然后將遍歷存儲著數(shù)據(jù)變化的全局?jǐn)?shù)組,將所有數(shù)組里數(shù)據(jù)確定先后優(yōu)先級,最終合并成一套需要展示到頁面上的數(shù)據(jù),執(zhí)行頁面渲染操作操作。

異步隊(duì)列執(zhí)行后,存儲頁面變化的全局?jǐn)?shù)組得到遍歷執(zhí)行,執(zhí)行的時候會進(jìn)行一些篩查操作,將重復(fù)操作過的數(shù)據(jù)進(jìn)行處理,實(shí)際就是先賦值的丟棄不渲染,最終按照優(yōu)先級最終組合成一套數(shù)據(jù)渲染。

這里觸發(fā)渲染的異步API優(yōu)先考慮Promise,其次MutationObserver,如果沒有MutationObserver的話,會考慮setImmediate,沒有setImmediate的話最后考慮是setTimeout。

接下來在源碼層面梳理一下的Vue的異步渲染過程。

深入解讀VUE中的異步渲染的實(shí)現(xiàn)

接下來從源碼角度一步一分析一下。

1、當(dāng)我們使用this.val=’343’賦值的時候,val屬性所綁定的Object.defineProperty的setter函數(shù)觸發(fā),setter函數(shù)將所訂閱的notify函數(shù)觸發(fā)執(zhí)行。

defineReactive() { ... set: function reactiveSetter (newVal) { ... dep.notify(); ... } ...}

2、notify函數(shù)中,將所有的訂閱組件watcher中的update方法執(zhí)行一遍。

Dep.prototype.notify = function notify () { // 拷貝所有組件的watcher var subs = this.subs.slice(); ... for (var i = 0, l = subs.length; i < l; i++) { subs[i].update(); }};

深入解讀VUE中的異步渲染的實(shí)現(xiàn)

3、update函數(shù)得到執(zhí)行后,默認(rèn)情況下lazy是false,sync也是false,直接進(jìn)入把所有響應(yīng)變化存儲進(jìn)全局?jǐn)?shù)組queueWatcher函數(shù)下。

Watcher.prototype.update = function update () { if (this.lazy) { this.dirty = true; } else if (this.sync) { this.run(); } else { queueWatcher(this); }};

深入解讀VUE中的異步渲染的實(shí)現(xiàn)

4、queueWatcher函數(shù)里,會先將組件的watcher存進(jìn)全局?jǐn)?shù)組變量queue里。默認(rèn)情況下config.async是true,直接進(jìn)入nextTick的函數(shù)執(zhí)行,nextTick是一個瀏覽器異步API實(shí)現(xiàn)的方法,它的回調(diào)函數(shù)是flushSchedulerQueue函數(shù)。

function queueWatcher (watcher) { ... // 在全局隊(duì)列里存儲將要響應(yīng)的變化update函數(shù) queue.push(watcher); ... // 當(dāng)async配置是false的時候,頁面更新是同步的 if (!config.async) { flushSchedulerQueue(); return } // 將頁面更新函數(shù)放進(jìn)異步API里執(zhí)行,同步代碼執(zhí)行完開始執(zhí)行更新頁面函數(shù) nextTick(flushSchedulerQueue);}

深入解讀VUE中的異步渲染的實(shí)現(xiàn)

5、nextTick函數(shù)的執(zhí)行后,傳入的flushSchedulerQueue函數(shù)又一次push進(jìn)callbacks全局?jǐn)?shù)組里,pending在初始情況下是false,這時候?qū)⒂|發(fā)timerFunc。

function nextTick (cb, ctx) { var _resolve; callbacks.push(function () { if (cb) { try { cb.call(ctx); } catch (e) { handleError(e, ctx, ’nextTick’); } } else if (_resolve) { _resolve(ctx); } }); if (!pending) { pending = true; timerFunc(); } // $flow-disable-line if (!cb && typeof Promise !== ’undefined’) { return new Promise(function (resolve) { _resolve = resolve; }) }}

6、timerFunc函數(shù)是由瀏覽器的Promise、MutationObserver、setImmediate、setTimeout這些異步API實(shí)現(xiàn)的,異步API的回調(diào)函數(shù)是flushCallbacks函數(shù)。

var timerFunc;// 這里Vue內(nèi)部對于異步API的選用,由Promise、MutationObserver、setImmediate、setTimeout里取一個// 取用的規(guī)則是 Promise存在取由Promise,不存在取MutationObserver,MutationObserver不存在setImmediate,// setImmediate不存在setTimeout。if (typeof Promise !== ’undefined’ && isNative(Promise)) { var p = Promise.resolve(); timerFunc = function () { p.then(flushCallbacks); if (isIOS) { setTimeout(noop); } }; isUsingMicroTask = true;} else if (!isIE && typeof MutationObserver !== ’undefined’ && (isNative(MutationObserver) ||// PhantomJS and iOS 7.x MutationObserver.toString() === ’[object MutationObserverConstructor]’)){ var counter = 1; var observer = new MutationObserver(flushCallbacks); var textNode = document.createTextNode(String(counter)); observer.observe(textNode, {characterData: true}); timerFunc = function () { counter = (counter + 1) % 2; textNode.data = String(counter); }; isUsingMicroTask = true; } else if (typeof setImmediate !== ’undefined’ && isNative(setImmediate)) { timerFunc = function () {setImmediate(flushCallbacks); }; } else { timerFunc = function () { setTimeout(flushCallbacks, 0); };}

7、flushCallbacks函數(shù)中將遍歷執(zhí)行nextTick里push的callback全局?jǐn)?shù)組,全局callback數(shù)組中實(shí)際是第5步的push的flushSchedulerQueue的執(zhí)行函數(shù)。

// 將nextTick里push進(jìn)去的flushSchedulerQueue函數(shù)進(jìn)行for循環(huán)依次調(diào)用function flushCallbacks () { pending = false; var copies = callbacks.slice(0); callbacks.length = 0; for (var i = 0; i < copies.length; i++) { copies[i](); }}

8、callback遍歷執(zhí)行的flushSchedulerQueue函數(shù)中,flushSchedulerQueue里先按照id進(jìn)行了優(yōu)先級排序,接下來將第4步中的存儲watcher對象全局queue遍歷執(zhí)行,觸發(fā)渲染函數(shù)watcher.run。

function flushSchedulerQueue () {var watcher, id;// 安裝id從小到大開始排序,越小的越前觸發(fā)的updatequeue.sort(function (a, b) { return a.id - b.id; });// queue是全局?jǐn)?shù)組,它在queueWatcher函數(shù)里,每次update觸發(fā)的時候?qū)?dāng)時的watcher,push進(jìn)去 for (index = 0; index < queue.length; index++) { ... watcher.run(); // 渲染 ... }}

9、watcher.run的實(shí)現(xiàn)在構(gòu)造函數(shù)Watcher原型鏈上,初始狀態(tài)下active屬性為true,直接執(zhí)行Watcher原型鏈的set方法。

Watcher.prototype.run = function run () { if (this.active) { var value = this.get(); ... }};

10、get函數(shù)中,將實(shí)例watcher對象push到全局?jǐn)?shù)組中,開始調(diào)用實(shí)例的getter方法,執(zhí)行完畢后,將watcher對象從全局?jǐn)?shù)組彈出,并且清除已經(jīng)渲染過的依賴實(shí)例。

Watcher.prototype.get = function get () { pushTarget(this); // 將實(shí)例push到全局?jǐn)?shù)組targetStack var vm = this.vm; value = this.getter.call(vm, vm); ...}

11、實(shí)例的getter方法實(shí)際是在實(shí)例化的時候傳入的函數(shù),也就是下面vm的真正更新函數(shù)_update。

function () { vm._update(vm._render(), hydrating);};

12、實(shí)例的_update函數(shù)執(zhí)行后,將會把兩次的虛擬節(jié)點(diǎn)傳入傳入vm的 patch 方法執(zhí)行渲染操作。

Vue.prototype._update = function (vnode, hydrating) { var vm = this; ... var prevVnode = vm._vnode; vm._vnode = vnode; if (!prevVnode) { // initial render vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */); } else { // updates vm.$el = vm.__patch__(prevVnode, vnode); } ...};

nextTick的實(shí)現(xiàn)原理

首先nextTick并不是瀏覽器本身提供的一個異步API,而是Vue中,用過由瀏覽器本身提供的原生異步API封裝而成的一個異步封裝方法,上面第5第6段是它的實(shí)現(xiàn)源碼。

它對于瀏覽器異步API的選用規(guī)則如下,Promise存在取由Promise.then,不存在Promise則取MutationObserver,MutationObserver不存在setImmediate,setImmediate不存在最后取setTimeout來實(shí)現(xiàn)。

從上面的取用規(guī)則也可以看出來,nextTick即有可能是微任務(wù),也有可能是宏任務(wù),從優(yōu)先去Promise和MutationObserver可以看出nextTick優(yōu)先微任務(wù),其次是setImmediate和setTimeout宏任務(wù)。

對于微任務(wù)與宏任務(wù)的區(qū)別這里不深入,只要記得同步代碼執(zhí)行完畢之后,優(yōu)先執(zhí)行微任務(wù),其次才會執(zhí)行宏任務(wù)。

Vue能不能同步渲染?

1、 Vue.config.async = false

當(dāng)然是可以的,在第四段源碼里,我們能看到如下一段,當(dāng)config里的async的值為為false的情況下,并沒有將flushSchedulerQueue加到nextTick里,而是直接執(zhí)行了flushSchedulerQueue,就相當(dāng)于把本次data里的值變化時,頁面做了同步渲染。

function queueWatcher (watcher) { ... // 在全局隊(duì)列里存儲將要響應(yīng)的變化update函數(shù) queue.push(watcher); ... // 當(dāng)async配置是false的時候,頁面更新是同步的 if (!config.async) { flushSchedulerQueue(); return } // 將頁面更新函數(shù)放進(jìn)異步API里執(zhí)行,同步代碼執(zhí)行完開始執(zhí)行更新頁面函數(shù) nextTick(flushSchedulerQueue);}

在我們的開發(fā)代碼里,只需要加入下一句即可讓你的頁面渲染同步進(jìn)行。

import Vue from ’Vue’Vue.config.async = false

2、this._watcher.sync = true

在Watch的update方法執(zhí)行源碼里,可以看到當(dāng)this.sync為true時,這時候的渲染也是同步的。

Watcher.prototype.update = function update () { if (this.lazy) { this.dirty = true; } else if (this.sync) { this.run(); } else { queueWatcher(this); }};

在開發(fā)代碼中,需要將本次watcher的sync屬性修改為true,對于watcher的sync屬性變化只需要在需要同步渲染的數(shù)據(jù)變化操作前執(zhí)行this._watcher.sync=true,這時候則會同步執(zhí)行頁面渲染動作。

像下面的寫法中,頁面會渲染出val為1,而不會渲染出2,最終渲染的結(jié)果是3,但是官網(wǎng)未推薦該用法,請慎用。

new Vue({ el: ’#app’, sync: true, template: ’<div>{{val}}</div>’, data () { return { val: 0 } }, mounted () { this._watcher.sync = true this.val = 1 debugger this._watcher.sync = false this.val = 2 this.val = 3 }})

總結(jié)

本文中介紹了Vue中為什么采用異步渲染頁面的原因,并且從源碼的角度深入剖析了整個渲染前的操作鏈路,同時剖析出Vue中的異步方法nextTick的實(shí)現(xiàn)與原生的異步API直接的聯(lián)系。最后也從源碼角度下了解到,Vue并非不能同步渲染,當(dāng)我們的頁面中需要同步渲染時,做適當(dāng)?shù)呐渲眉纯蓾M足。

References

[1] https://github.com/vuejs/vue

[2] https://cn.vuejs.org/

到此這篇關(guān)于深入解讀VUE中的異步渲染的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)深入解讀VUE中的異步渲染內(nèi)容請搜索好吧啦網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持好吧啦網(wǎng)!

標(biāo)簽: Vue
相關(guān)文章:
日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区
亚洲精品一二三区区别| 动漫av一区| 精品一区在线| 亚洲精品国产偷自在线观看| 在线日韩电影| 夜夜嗨一区二区三区| 日本 国产 欧美色综合| 日本一不卡视频| 国产精品香蕉| 美女久久99| 成人影视亚洲图片在线| 亲子伦视频一区二区三区| 精品91久久久久| 日本aⅴ免费视频一区二区三区| 日韩激情一区二区| 久久精品五月| 久久人人99| 日韩欧美三区| 精品精品99| 欧美日韩精品一本二本三本| 影音先锋久久精品| 麻豆一区二区三| 欧美日韩在线观看视频小说| 视频一区二区不卡| 国产精品一线天粉嫩av| 日韩欧美字幕| 三级亚洲高清视频| 捆绑调教美女网站视频一区| 亚洲午夜91| 国产麻豆一区二区三区精品视频| xxxxx性欧美特大| 石原莉奈在线亚洲二区| 国产精品精品| 亚洲精品在线国产| 97人人精品| 免费在线观看日韩欧美| 国产精品最新自拍| 国户精品久久久久久久久久久不卡 | 欧美精品aa| 久久福利精品| 亚洲免费观看| 成人日韩av| 成人在线视频免费看| 午夜免费一区| 欧美激情在线精品一区二区三区| 波多野结衣一区| 国产精品亚洲欧美日韩一区在线| 国产一区亚洲| 欧美亚洲免费| 亚洲高清二区| 精品国产黄a∨片高清在线| 视频一区二区三区入口| 蜜桃成人精品| 国产精品亚洲二区| 视频一区二区三区中文字幕| 亚洲午夜天堂| 日本欧美一区二区| 国产在线日韩| 日本一区二区高清不卡| 日韩二区三区在线观看| 国产一区二区高清| 欧美午夜精彩| 欧美少妇精品| 国产专区精品| 日本不卡视频在线观看 | 日韩国产欧美在线播放| 尤物tv在线精品| 福利精品在线| 国产精品国码视频| 亚洲a级精品| 亚洲一区中文| 久久视频精品| 成人午夜精品| 日产精品一区二区| 精品国产麻豆| 美女在线视频一区| 欧美午夜网站| 免费成人av在线播放| 欧美日韩激情| 国产91精品对白在线播放| 精品中文字幕一区二区三区四区| 日本午夜精品视频在线观看| 国产视频一区免费看| 久久一区二区中文字幕| 成人看片网站| 精品成人免费一区二区在线播放| 久草免费在线视频| 麻豆视频在线看| 国产suv精品一区二区四区视频| 国产精品1区| 欧美国产另类| 精品视频高潮| 久久久亚洲欧洲日产| 国产精品hd| 欧美国产极品| 精品欠久久久中文字幕加勒比| 免费精品一区| 国产成人在线中文字幕| 91综合网人人| 在线一区视频观看| 国产高清一区| 蜜桃视频一区二区| 日韩欧美美女在线观看| 国产精品午夜av| 美女精品一区二区| 在线亚洲人成| 美女少妇全过程你懂的久久| 久久久精品网| 国产女优一区| 日韩毛片网站| 精品精品国产三级a∨在线| 中文字幕在线高清| 99久久久久国产精品| 99pao成人国产永久免费视频| 亚洲综合日本| 7777精品| 成人亚洲精品| 欧美日韩国产亚洲一区| 日韩极品在线观看| 激情久久一区二区| 亚洲一级黄色| 亚洲一区二区小说| 日韩av电影一区| 三上亚洲一区二区| 婷婷成人基地| 亚洲欧美日本国产专区一区| 亚洲精品系列| 欧美激情在线精品一区二区三区| sm捆绑调教国产免费网站在线观看| 日本韩国欧美超级黄在线观看| 亚洲精品1区2区| 91成人福利| 欧美不卡高清一区二区三区| 国产精品婷婷| 麻豆久久一区二区| 亚洲黄色影院| 麻豆精品久久| japanese国产精品| 欧美日韩一区二区三区不卡视频 | 日韩黄色大片网站| 午夜日韩在线| 国产福利资源一区| 亚洲成人一区在线观看| 亚洲深夜福利在线观看| 鲁大师精品99久久久| 狠狠爱成人网| 国产精品草草| 欧美日韩激情| 久久不见久久见免费视频7| 激情久久久久久久| 国产精品久久久久av蜜臀| 欧美手机在线| 免费亚洲婷婷| 国产女优一区| 日韩国产一区| 日本不卡一区二区| 亚洲黑丝一区二区| 国产精品美女在线观看直播| 久久精选视频| 国产极品一区| 蜜桃视频第一区免费观看| 欧美天堂视频| 欧美亚洲网站| 男人的天堂亚洲一区| 日本久久成人网| 久久精品亚洲一区二区| 亚洲精品麻豆| 婷婷国产精品| 成人一区不卡| 国产九九精品| 中文一区一区三区免费在线观| 日本久久综合| 欧美日韩亚洲一区在线观看| 在线一区免费| 桃色一区二区| 免费精品一区| 欧美日韩一区二区三区不卡视频| 亚洲资源av| 欧美精品九九| 久久国产直播| 成人在线免费观看网站| 欧美日韩夜夜| 亚洲人成毛片在线播放女女| 欧洲在线一区| 六月婷婷综合| 欧美精品成人| 国产亚洲欧美日韩精品一区二区三区| 欧美 日韩 国产一区二区在线视频| 国产极品一区| 欧美亚洲福利| 97精品资源在线观看| 中文字幕亚洲在线观看| 99久久99久久精品国产片果冰| 福利一区和二区| 精品99久久| 精品国产亚洲日本| 精品中国亚洲| 久草精品视频| 国产精品黑丝在线播放| 国产精品久久久久久久久久10秀|