电脑知识|欧美黑人一区二区三区|软件|欧美黑人一级爽快片淫片高清|系统|欧美黑人狂野猛交老妇|数据库|服务器|编程开发|网络运营|知识问答|技术教程文章 - 好吧啦网

您的位置:首頁技術文章
文章詳情頁

深入解讀VUE中的異步渲染的實現

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

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

同時也會了解在Vue中的異步方法NextTick的源碼實現,看一看NextTick方法與瀏覽器的異步API有何聯系。

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

什么是異步渲染?

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

從一個例子體驗一下異步渲染機制。

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屬性進行了兩次賦值,如果頁面渲染與數據的變化完全同步的話,頁面應該是在mounted里有兩次渲染。

而由于Vue內部的渲染機制,實際上頁面只會渲染一次,把第一次的賦值所帶來的的響應與第二次的賦值所帶來的的響應進行一次合并,將最終的val只做一次頁面渲染。

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

熟悉React的同學,應該很快能想到多次執行setState函數時,頁面render的渲染觸發,實際上與上面所說的Vue的異步渲染有異曲同工之妙。

Vue為什么要異步渲染?

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

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

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

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

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

Vue中如何實現異步渲染?

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

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

異步隊列執行后,存儲頁面變化的全局數組得到遍歷執行,執行的時候會進行一些篩查操作,將重復操作過的數據進行處理,實際就是先賦值的丟棄不渲染,最終按照優先級最終組合成一套數據渲染。

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

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

深入解讀VUE中的異步渲染的實現

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

1、當我們使用this.val=’343’賦值的時候,val屬性所綁定的Object.defineProperty的setter函數觸發,setter函數將所訂閱的notify函數觸發執行。

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

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

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中的異步渲染的實現

3、update函數得到執行后,默認情況下lazy是false,sync也是false,直接進入把所有響應變化存儲進全局數組queueWatcher函數下。

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

深入解讀VUE中的異步渲染的實現

4、queueWatcher函數里,會先將組件的watcher存進全局數組變量queue里。默認情況下config.async是true,直接進入nextTick的函數執行,nextTick是一個瀏覽器異步API實現的方法,它的回調函數是flushSchedulerQueue函數。

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

深入解讀VUE中的異步渲染的實現

5、nextTick函數的執行后,傳入的flushSchedulerQueue函數又一次push進callbacks全局數組里,pending在初始情況下是false,這時候將觸發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函數是由瀏覽器的Promise、MutationObserver、setImmediate、setTimeout這些異步API實現的,異步API的回調函數是flushCallbacks函數。

var timerFunc;// 這里Vue內部對于異步API的選用,由Promise、MutationObserver、setImmediate、setTimeout里取一個// 取用的規則是 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函數中將遍歷執行nextTick里push的callback全局數組,全局callback數組中實際是第5步的push的flushSchedulerQueue的執行函數。

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

8、callback遍歷執行的flushSchedulerQueue函數中,flushSchedulerQueue里先按照id進行了優先級排序,接下來將第4步中的存儲watcher對象全局queue遍歷執行,觸發渲染函數watcher.run。

function flushSchedulerQueue () {var watcher, id;// 安裝id從小到大開始排序,越小的越前觸發的updatequeue.sort(function (a, b) { return a.id - b.id; });// queue是全局數組,它在queueWatcher函數里,每次update觸發的時候將當時的watcher,push進去 for (index = 0; index < queue.length; index++) { ... watcher.run(); // 渲染 ... }}

9、watcher.run的實現在構造函數Watcher原型鏈上,初始狀態下active屬性為true,直接執行Watcher原型鏈的set方法。

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

10、get函數中,將實例watcher對象push到全局數組中,開始調用實例的getter方法,執行完畢后,將watcher對象從全局數組彈出,并且清除已經渲染過的依賴實例。

Watcher.prototype.get = function get () { pushTarget(this); // 將實例push到全局數組targetStack var vm = this.vm; value = this.getter.call(vm, vm); ...}

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

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

12、實例的_update函數執行后,將會把兩次的虛擬節點傳入傳入vm的 patch 方法執行渲染操作。

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的實現原理

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

它對于瀏覽器異步API的選用規則如下,Promise存在取由Promise.then,不存在Promise則取MutationObserver,MutationObserver不存在setImmediate,setImmediate不存在最后取setTimeout來實現。

從上面的取用規則也可以看出來,nextTick即有可能是微任務,也有可能是宏任務,從優先去Promise和MutationObserver可以看出nextTick優先微任務,其次是setImmediate和setTimeout宏任務。

對于微任務與宏任務的區別這里不深入,只要記得同步代碼執行完畢之后,優先執行微任務,其次才會執行宏任務。

Vue能不能同步渲染?

1、 Vue.config.async = false

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

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

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

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

2、this._watcher.sync = true

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

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

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

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

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 }})

總結

本文中介紹了Vue中為什么采用異步渲染頁面的原因,并且從源碼的角度深入剖析了整個渲染前的操作鏈路,同時剖析出Vue中的異步方法nextTick的實現與原生的異步API直接的聯系。最后也從源碼角度下了解到,Vue并非不能同步渲染,當我們的頁面中需要同步渲染時,做適當的配置即可滿足。

References

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

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

到此這篇關于深入解讀VUE中的異步渲染的實現的文章就介紹到這了,更多相關深入解讀VUE中的異步渲染內容請搜索好吧啦網以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持好吧啦網!

標簽: Vue
相關文章:
主站蜘蛛池模板: 水平垂直燃烧试验仪-灼热丝试验仪-漏电起痕试验仪-针焰试验仪-塑料材料燃烧检测设备-IP防水试验机 | 冲锋衣滑雪服厂家-冲锋衣定制工厂-滑雪服加工厂-广东睿牛户外(S-GERT) | 钢格栅板_钢格板网_格栅板-做专业的热镀锌钢格栅板厂家-安平县迎瑞丝网制造有限公司 | 济南冷库安装-山东冷库设计|建造|冷库维修-山东齐雪制冷设备有限公司 | 电动百叶窗,开窗器,电动遮阳百叶,电动开窗机生产厂家-徐州鑫友工控科技发展有限公司 | 1000帧高速摄像机|工业高速相机厂家|科天健光电技术 | 拖链电缆_柔性电缆_伺服电缆_坦克链电缆-深圳市顺电工业电缆有限公司 | 渗透仪-直剪仪-三轴仪|苏州昱创百科| 电缆接头-防爆电缆接头-格兰头-金属电缆接头-防爆填料函 | 宿松新闻网 宿松网|宿松在线|宿松门户|安徽宿松(直管县)|宿松新闻综合网站|宿松官方新闻发布 | 基业箱_环网柜_配电柜厂家_开关柜厂家_开关断路器-东莞基业电气设备有限公司 | 地磅-电子地磅维修-电子吊秤-汽车衡-无人值守系统-公路治超-鹰牌衡器 | 全自动包装秤_全自动上袋机_全自动套袋机_高位码垛机_全自动包装码垛系统生产线-三维汉界机器(山东)股份有限公司 | 礼仪庆典公司,礼仪策划公司,庆典公司,演出公司,演艺公司,年会酒会,生日寿宴,动工仪式,开工仪式,奠基典礼,商务会议,竣工落成,乔迁揭牌,签约启动-东莞市开门红文化传媒有限公司 | 酵素生产厂家_酵素OEM_酵素加盟_酵素ODM_酵素原料厂家_厦门益力康 | 水冷散热器_水冷电子散热器_大功率散热器_水冷板散热器厂家-河源市恒光辉散热器有限公司 | 华溶溶出仪-Memmert稳定箱-上海协烁仪器科技有限公司 | 丹尼克尔拧紧枪_自动送钉机_智能电批_柔性振动盘_螺丝供料器品牌 | 大立教育官网-一级建造师培训-二级建造师培训-造价工程师-安全工程师-监理工程师考试培训 | 青岛侦探_青岛侦探事务所_青岛劝退小三_青岛调查出轨取证公司_青岛婚外情取证-青岛探真调查事务所 | 根系分析仪,大米外观品质检测仪,考种仪,藻类鉴定计数仪,叶面积仪,菌落计数仪,抑菌圈测量仪,抗生素效价测定仪,植物表型仪,冠层分析仪-杭州万深检测仪器网 | 煤粉取样器-射油器-便携式等速飞灰取样器-连灵动 | 理化生实验室设备,吊装实验室设备,顶装实验室设备,实验室成套设备厂家,校园功能室设备,智慧书法教室方案 - 东莞市惠森教学设备有限公司 | 珠海白蚁防治_珠海灭鼠_珠海杀虫灭鼠_珠海灭蟑螂_珠海酒店消杀_珠海工厂杀虫灭鼠_立净虫控防治服务有限公司 | 空气净化器租赁,空气净化器出租,全国直租_奥司汀净化器租赁 | 压力控制器,差压控制器,温度控制器,防爆压力控制器,防爆温度控制器,防爆差压控制器-常州天利智能控制股份有限公司 | 高考志愿规划师_高考规划师_高考培训师_高报师_升学规划师_高考志愿规划师培训认证机构「向阳生涯」 | 润东方环保空调,冷风机,厂房车间降温设备-20年深圳环保空调生产厂家 | 欧洲MV日韩MV国产_人妻无码一区二区三区免费_少妇被 到高潮喷出白浆av_精品少妇自慰到喷水AV网站 | 上海电子秤厂家,电子秤厂家价格,上海吊秤厂家,吊秤供应价格-上海佳宜电子科技有限公司 | 罗茨真空机组,立式无油往复真空泵,2BV水环真空泵-力侨真空科技 | 承插管件_不锈钢承插管件_锻钢高压管件-温州科正阀门管件有限公司 | 一点车讯-汽车网站,每天一点最新车讯! | 冷藏车-东风吸污车-纯电动环卫车-污水净化车-应急特勤保障车-程力专汽厂家-程力专用汽车股份有限公司销售二十一分公司 | 绿萝净除甲醛|深圳除甲醛公司|测甲醛怎么收费|培训机构|电影院|办公室|车内|室内除甲醛案例|原理|方法|价格立马咨询 | 南京租车,南京汽车租赁,南京包车,南京会议租车-南京七熹租车 | 球磨机,节能球磨机价格,水泥球磨机厂家,粉煤灰球磨机-吉宏机械制造有限公司 | 有机废气处理-rto焚烧炉-催化燃烧设备-VOC冷凝回收装置-三梯环境 | 橡胶膜片,夹布膜片,橡胶隔膜密封,泵阀设备密封膜片-衡水汉丰橡塑科技公司网站 | 高柔性拖链电缆-聚氨酯卷筒电缆-柔性屏蔽电缆厂家-玖泰电缆 | 多功能真空滤油机_润滑油全自动滤油机_高效真空滤油机价格-重庆润华通驰 |