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

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

 更新時間:2020年06月19日 14:25:43   作者:Noah77  
這篇文章主要介紹了深入解讀VUE中的異步渲染的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧

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

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

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

什么是異步渲染?

這個問題應該先要做一個前提補充,當數(shù)據(jù)在同步變化的時候,頁面訂閱的響應操作為什么不會與數(shù)據(jù)變化完全對應,而是在所有的數(shù)據(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屬性進行了兩次賦值,如果頁面渲染與數(shù)據(jù)的變化完全同步的話,頁面應該是在mounted里有兩次渲染。

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

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

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

Vue為什么要異步渲染?

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

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

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

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

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

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

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

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

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

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

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

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

1、當我們使用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(); }};

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

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

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

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

5、nextTick函數(shù)的執(zhí)行后,傳入的flushSchedulerQueue函數(shù)又一次push進callbacks全局數(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實現(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全局數(shù)組,全局callback數(shù)組中實際是第5步的push的flushSchedulerQueue的執(zhí)行函數(shù)。

// 將nextTick里push進去的flushSchedulerQueue函數(shù)進行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進行了優(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是全局數(shù)組,它在queueWatcher函數(shù)里,
每次update觸發(fā)的時候?qū)敃r的watcher,push進去 for (index = 0; index < queue.length; index++) { 
  ...  watcher.run(); // 渲染  ... }}

9、watcher.run的實現(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ù)中,將實例watcher對象push到全局數(shù)組中,開始調(diào)用實例的getter方法,執(zhí)行完畢后,將watcher對象從全局數(shù)組彈出,并且清除已經(jīng)渲染過的依賴實例。

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

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

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

12、實例的_update函數(shù)執(zhí)行后,將會把兩次的虛擬節(jié)點傳入傳入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的實現(xiàn)原理

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

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

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

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

Vue能不能同步渲染?

1、 Vue.config.async = false

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

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

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

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

2、this._watcher.sync = true

在Watch的update方法執(zhí)行源碼里,可以看到當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的實現(xiàn)與原生的異步API直接的聯(lián)系。最后也從源碼角度下了解到,Vue并非不能同步渲染,當我們的頁面中需要同步渲染時,做適當?shù)呐渲眉纯蓾M足。

References

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

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

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

相關文章

  • vue中選中多個選項并且改變選中的樣式的實例代碼

    vue中選中多個選項并且改變選中的樣式的實例代碼

    這篇文章主要介紹了vue中選中多個選項并且改變選中的樣式,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-09-09
  • vue實現(xiàn)圖片平鋪方式

    vue實現(xiàn)圖片平鋪方式

    在Vue中,實現(xiàn)圖片或組件的平鋪布局并允許用戶進行修改,可以通過數(shù)據(jù)綁定、模板編寫與交互設計來實現(xiàn)圖片平鋪,使用v-for指令和動態(tài)組件,可以創(chuàng)建可編輯的組件平鋪布局,允許用戶通過點擊觸發(fā)編輯操作,通過外部編輯面板修改屬性后保存更改
    2024-10-10
  • Vue+UpLoad實現(xiàn)上傳預覽和刪除圖片的實踐

    Vue+UpLoad實現(xiàn)上傳預覽和刪除圖片的實踐

    本文主要介紹了Vue+UpLoad實現(xiàn)上傳預覽和刪除圖片的實踐,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-02-02
  • 全面詳解vue種數(shù)組去重的12種方法示例

    全面詳解vue種數(shù)組去重的12種方法示例

    這篇文章主要介紹了vue數(shù)組去重的12種方法示例全面詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-07-07
  • 淺談在vue中用webpack打包之后運行文件的問題以及相關配置方法

    淺談在vue中用webpack打包之后運行文件的問題以及相關配置方法

    下面小編就為大家分享一篇淺談在vue中用webpack打包之后運行文件的問題以及相關配置方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-02-02
  • 基于vue3+TypeScript實現(xiàn)一個簡易的Calendar組件

    基于vue3+TypeScript實現(xiàn)一個簡易的Calendar組件

    最近在學習 react,在學習到使用 react 開發(fā) Calendar 組件的時候,突然聯(lián)想到使用 vue 進行 Calendar 功能的實現(xiàn),因為目前使用的技術棧是 vue,剛好可以加深下對 vue3 和 ts 的使用印象,所以本文給大家介紹了基于vue3+TypeScript實現(xiàn)一個簡易的Calendar組件
    2024-05-05
  • VUE+Element環(huán)境搭建與安裝的方法步驟

    VUE+Element環(huán)境搭建與安裝的方法步驟

    這篇文章主要介紹了VUE+Element環(huán)境搭建與安裝的方法步驟,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2019-01-01
  • unplugin-auto-import的配置以及eslint報錯解決詳解

    unplugin-auto-import的配置以及eslint報錯解決詳解

    unplugin-auto-import?解決了vue3-hook、vue-router、useVue等多個插件的自動導入,也支持自定義插件的自動導入,是一個功能強大的typescript支持工具,這篇文章主要給大家介紹了關于unplugin-auto-import的配置以及eslint報錯解決的相關資料,需要的朋友可以參考下
    2022-08-08
  • Element 默認勾選表格 toggleRowSelection的實現(xiàn)

    Element 默認勾選表格 toggleRowSelection的實現(xiàn)

    這篇文章主要介紹了Element 默認勾選表格 toggleRowSelection的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-09-09
  • vue使用el-table篩選tree樹形結(jié)構(gòu)的數(shù)據(jù)問題

    vue使用el-table篩選tree樹形結(jié)構(gòu)的數(shù)據(jù)問題

    這篇文章主要介紹了vue使用el-table篩選tree樹形結(jié)構(gòu)的數(shù)據(jù)問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-07-07

最新評論