自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Vue實(shí)現(xiàn)原理+前端性能優(yōu)化

開(kāi)發(fā) 前端
Vue在前端框架中的地位就像曾經(jīng)的jQuery,由于其簡(jiǎn)單易懂、開(kāi)發(fā)效率高,已經(jīng)成為了前端工程師必不可少的技能之一。

 一、Vue實(shí)現(xiàn)原理

1、Vue簡(jiǎn)介

現(xiàn)在的大前端時(shí)代,是一個(gè)動(dòng)蕩紛爭(zhēng)的時(shí)代,江湖中已經(jīng)分成了很多門(mén)派,主要以Vue,React還有Angular為首,形成前端框架三足鼎立的局勢(shì)。Vue在前端框架中的地位就像曾經(jīng)的jQuery,由于其簡(jiǎn)單易懂、開(kāi)發(fā)效率高,已經(jīng)成為了前端工程師必不可少的技能之一。

Vue是一種漸進(jìn)式JavaScript框架,完美融合了第三方插件和UI組件庫(kù),它和jQuery最大的區(qū)別在于,Vue無(wú)需開(kāi)發(fā)人員直接操作DOM節(jié)點(diǎn),就可以改變頁(yè)面渲染內(nèi)容,在應(yīng)用開(kāi)發(fā)者具有一定的HTML、CSS、JavaScript的基礎(chǔ)上,能夠快速上手,開(kāi)發(fā)出優(yōu)雅、簡(jiǎn)潔的應(yīng)用程序模塊。

但是我們提及Vue的時(shí)候,更多的是關(guān)注它的用法,而不是學(xué)習(xí)它是如何解決前端問(wèn)題的,這多少有點(diǎn)亞健康。有前端開(kāi)發(fā)經(jīng)驗(yàn)的人,一定在開(kāi)發(fā)過(guò)程中遇到過(guò)奇奇怪怪的問(wèn)題,然后稀里糊涂地解決,倘若再次遇到相似的問(wèn)題,便再次手足無(wú)措,作為一名前端工程師,在遇到問(wèn)題的時(shí)候我們是否能準(zhǔn)確定位產(chǎn)生問(wèn)題的原因并及時(shí)解決,主要取決于我們對(duì)前端框架的理解是否足夠深入。

2、Vue實(shí)現(xiàn)原理

2.1 虛擬DOM(Virtual DOM)

隨著時(shí)代的發(fā)展,Web應(yīng)用的頁(yè)面交互效果越來(lái)越復(fù)雜,頁(yè)面功能越來(lái)越豐富,需要維護(hù)的狀態(tài)越來(lái)越多,DOM操作也越來(lái)越頻繁。DOM操作雖然簡(jiǎn)單易用,但是會(huì)產(chǎn)生不好維護(hù)的問(wèn)題。

在程序執(zhí)行的過(guò)程中,Watcher初始化時(shí)會(huì)將每一個(gè)節(jié)點(diǎn)和狀態(tài)進(jìn)行一一關(guān)聯(lián)和映射,setter監(jiān)聽(tīng)到Data的狀態(tài)發(fā)生改變后,就會(huì)通知Watcher,Watcher會(huì)將這些變化通知曾經(jīng)記錄過(guò)的DOM以及跟這些狀態(tài)相關(guān)的節(jié)點(diǎn),從而觸發(fā)頁(yè)面的渲染過(guò)程。組件接收到狀態(tài)變化后,會(huì)通過(guò)編譯將模板轉(zhuǎn)換成渲染函數(shù)Render,執(zhí)行渲染函數(shù)就會(huì)得到一個(gè)虛擬DOM樹(shù),通過(guò)對(duì)比舊的虛擬DOM和新生成的虛擬DOM樹(shù),來(lái)更新對(duì)應(yīng)的實(shí)際DOM節(jié)點(diǎn),執(zhí)行頁(yè)面渲染。

主流前端框架幾乎都在使用虛擬DOM,但是在使用虛擬DOM的時(shí)候,Angular和React都無(wú)法確定具體是哪個(gè)狀態(tài)發(fā)生了變化,因此需要在舊的虛擬DOM和新的虛擬DOM之間進(jìn)行暴力對(duì)比,但Vue從1.0版本開(kāi)始,就通過(guò)細(xì)粒度的綁定來(lái)更新視圖,也就是說(shuō),當(dāng)狀態(tài)發(fā)生變化的時(shí)候Vue可以知道具體是哪個(gè)狀態(tài)哪些節(jié)點(diǎn)需要發(fā)生改變,從而對(duì)這個(gè)節(jié)點(diǎn)執(zhí)行更新,然而這種細(xì)粒度的變化偵測(cè)會(huì)有一些內(nèi)存開(kāi)銷(xiāo)影響性能,一個(gè)項(xiàng)目越復(fù)雜,開(kāi)銷(xiāo)就越大。

Vue從2.0版本后,為了優(yōu)化性能,引入了虛擬DOM,選擇了一個(gè)折中的方案,既不需要暴力對(duì)比整個(gè)新舊虛擬DOM,也不需要通過(guò)細(xì)粒度的綁定來(lái)實(shí)現(xiàn)視圖的更新,即以組件為單位進(jìn)行Watcher監(jiān)聽(tīng),也就是說(shuō)即便一個(gè)組件內(nèi)有多個(gè)節(jié)點(diǎn)使用了某個(gè)狀態(tài),也只需一個(gè)Watcher來(lái)監(jiān)聽(tīng)這個(gè)狀態(tài)的變化,當(dāng)這個(gè)狀態(tài)發(fā)生變化時(shí),Watcher通知組件,組件內(nèi)部通過(guò)虛擬DOM的方式去進(jìn)行節(jié)點(diǎn)的對(duì)比和重新渲染。

2.2 常用指令實(shí)現(xiàn)原理

指令是指Vue提供的以“v-”前綴的特性,當(dāng)指令中表達(dá)式的內(nèi)容發(fā)生變化時(shí),會(huì)連帶影響DOM內(nèi)容發(fā)生變化。Vue.directive全局API可以創(chuàng)建自定義指令,并獲取全局指令,除了自定義指令,Vue還內(nèi)置了一些開(kāi)發(fā)過(guò)程中常用的指令,如v-if、v-for等。在Vue模板解析時(shí),會(huì)將指令解析到AST,使用AST生成字符串的過(guò)程中實(shí)現(xiàn)指令的功能。

在解析模板時(shí),會(huì)將節(jié)點(diǎn)上的指令解析出來(lái)并添加到AST的directives屬性中,directives將數(shù)據(jù)發(fā)送到VNode中,在虛擬DOM進(jìn)行頁(yè)面渲染時(shí),會(huì)觸發(fā)某些鉤子函數(shù),當(dāng)鉤子函數(shù)被觸發(fā)后,就說(shuō)明指令已生效。

2.2.1 v-if指令原理

在應(yīng)用程序中使用v-if指令: 

  1. <div v-if="create">create if</div>  
  2. <div v-else>create else</div> 

在編譯階段生成: 

  1. (create)  
  2.  ? _c('div',[_v("create if")])  
  3.  : _c('div',[_v("create else")]) 

在代碼執(zhí)行時(shí),會(huì)根據(jù)create的值來(lái)選擇創(chuàng)建哪個(gè)節(jié)點(diǎn)。

2.2.2 v-for指令原理

在應(yīng)用程序中使用v-for指令: 

  1. <li v-for="(item,index) in list">{{item}}</li> 

在編譯階段生成: 

  1. _l((list), function(item, index){  
  2.  return _c('li',[  
  3.   _v(_s(item))  
  4.  ]) 
  5. }) 

_l是renderList的別名,執(zhí)行代碼時(shí),_l函數(shù)會(huì)循環(huán)list變量,調(diào)用第二個(gè)參數(shù)中傳遞的函數(shù),傳遞兩個(gè)參數(shù):item和index,當(dāng)_c函數(shù)被調(diào)用時(shí),會(huì)執(zhí)行_v函數(shù),創(chuàng)建一個(gè)節(jié)點(diǎn)。

2.2.3 自定義指令原理

在應(yīng)用程序中,指令的處理邏輯分別監(jiān)聽(tīng)了create函數(shù)、update函數(shù)以及destory函數(shù),具體實(shí)現(xiàn)如下: 

  1. export default {  
  2.  create: updateDirectives,  
  3.  update: updateDirectives,  
  4.  destory: function unbindDirectives (vnode){  
  5.   updateDirectives(vnode, emptyNode)  
  6.  }  

鉤子函數(shù)被觸發(fā)后,會(huì)執(zhí)行updateDirectives函數(shù),代碼如下: 

  1. function updateDirectives(oldVnode, vnode){  
  2.  if (oldVnode.data.directives || vnode.data.directives) {  
  3.   _update(oldVnode, vnode)  
  4.  }  

在該函數(shù)中,不論是否存在舊虛擬節(jié)點(diǎn),只要其中存在directives,就會(huì)執(zhí)行_update函數(shù),_update函數(shù)代碼如下: 

  1. function _update(oldVnode, vnode) {  
  2.  const isCreate = oldVnode === emptyNode  
  3.  const isDestory = vnode === emptyNode  
  4.  const oldDirs = normalizeDirectives(oldVnode.data.directives, oldVnode.context)  
  5.  const newDirs = normalizeDirectives(vnode.data.directives, vnode.context)  
  6.  const dirsWithInsert = []  
  7.  const dirsWithPostpatch = []  
  8.  let key, oldDir, dir  
  9.  for (key in newDirs) {  
  10.   oldDir = oldDirs[key]  
  11.   dir = newDirs[key]  
  12.   if (!oldDir) { //新指令觸發(fā)bind  
  13.    callHook(dir, 'bind', vnode, oldVnode)  
  14.    if (dir.def && dir.def.inserted) {  
  15.     dirsWithInsert.push(dir)  
  16.    }  
  17.   } else { //指令已存在觸發(fā)update  
  18.    dir.oldValue = oldDir.value  
  19.    callHook(dir, 'update', vnode, oldVnode)  
  20.    if (dir.def && dir.def.componentUpdated) {  
  21.     dirsWithPostpatch.push(dir)  
  22.    }  
  23.   }  
  24.  }  
  25.  if (dirsWithInsert.length) {  
  26.   const callInsert = () => {  
  27.    for (let i = 0; i < dirsWithInsert.length; i++) {  
  28.     callHook(dirsWithInsert[i], 'inserted', vnode, oldVnode)  
  29.    }  
  30.   }  
  31.   if (isCreate) {  
  32.    mergeVNodeHook(vnode, 'insert', callInsert) 
  33.    } else {  
  34.    callInsert()  
  35.   }  
  36.  }  
  37.  if (dirsWithPostpatch.length) {  
  38.   mergeVNodeHook(vnode, 'postpatch', () => {  
  39.    for(let i = 0; i < dirsWithPostpatch.length; i++) {  
  40.     callHook(dirsWithPostpatch[i], 'componentUpdated', vnode, oldVnode)  
  41.    }  
  42.   })  
  43.  }  
  44.  if (!isCreate) {  
  45.   for(key in oldDirs) {  
  46.    if (!newDirs[key]) {  
  47.     callHook(oldDirs[key], 'unbind', oldVnode, oldVnode, isDestory)  
  48.    }  
  49.   }  
  50.  }  

isCreate:判斷該虛擬節(jié)點(diǎn)是否是一個(gè)新建的節(jié)點(diǎn)。

isDistory:判斷是否刪除一個(gè)舊虛擬節(jié)點(diǎn)。

oldDirs:舊的指令集合,oldVnode中保存的指令。

newDirs:新的指令集合,vnode中保存的指令。

dirsWithInsert:觸發(fā)inserted指令鉤子函數(shù)的指令列表。

dirsWithPostpatch:觸發(fā)componentUpdated鉤子函數(shù)的指令列表。

通過(guò)normalizeDirectives函數(shù)將模板中使用的指令從用戶注冊(cè)的自定義指令集合中取出來(lái)的結(jié)果如下: 

  1.  
  2.  v-customize: {  
  3.   def: {inserted: f},  
  4.   modifiers: {},  
  5.   name: "customize",  
  6.   rawName: "v-customize"  
  7.  }  

自定義指令的代碼為: 

  1. Vue.directives('customize', {  
  2.  inserted: function (el) {  
  3.   el.customize()  
  4.  }  
  5. }) 

虛擬DOM在對(duì)比和渲染時(shí),會(huì)根據(jù)不同情景觸發(fā)不同的鉤子函數(shù),當(dāng)使用虛擬節(jié)點(diǎn)創(chuàng)建一個(gè)新的實(shí)際節(jié)點(diǎn)時(shí),會(huì)觸發(fā)create鉤子函數(shù),當(dāng)一個(gè)DOM節(jié)點(diǎn)插入到父節(jié)點(diǎn)時(shí),會(huì)觸發(fā)insert鉤子函數(shù)。

callHook函數(shù)執(zhí)行鉤子函數(shù)的方式如下: 

  1. function callHook(dir, hook, vnode, oldVnode, isDestory) {  
  2.  const fn = dir.def && dir.def[hook]  
  3.  if (fn) {  
  4.   try {  
  5.    fn(vnode.elm, dir, vnode, oldVnode, isDestory)  
  6.   } catch (e) {  
  7.    handleError(e, vnode.context, `directive ${dir.name} ${hook} hook`) 
  8.   }  
  9.  }  

callHook函數(shù)的參數(shù)意義分別為:

dir:指令對(duì)象。

hook:將要觸發(fā)的鉤子函數(shù)名。

vnode:新的虛擬節(jié)點(diǎn)。

oldVnode:舊的虛擬節(jié)點(diǎn)。

isDestory:判斷是否刪除一個(gè)舊虛擬節(jié)點(diǎn)。

虛擬DOM在渲染時(shí)會(huì)觸發(fā)的所有鉤子函數(shù)及其觸發(fā)機(jī)制如下:

需要注意的是,remove函數(shù)是只有一個(gè)元素從其父元素中移除時(shí)才會(huì)觸發(fā),如果該元素是被移除元素的子元素,則不會(huì)觸發(fā)remove函數(shù)。

二、前端性能優(yōu)化

前端在一個(gè)應(yīng)用中,主要承擔(dān)在用戶打開(kāi)一個(gè)頁(yè)面時(shí),發(fā)送請(qǐng)求到服務(wù)器,接收服務(wù)器返回的頁(yè)面進(jìn)行渲染并將渲染結(jié)果呈現(xiàn)給用戶的功能。

要提高前端性能需要從與用戶操作無(wú)關(guān)的客戶端和服務(wù)端交互和瀏覽器解析頁(yè)面著手,也就是從傳輸和渲染兩方面著手。

1、請(qǐng)求傳輸

1.1請(qǐng)求維度

基于目前前后端傳輸廣泛使用的HTTP 1.1協(xié)議,可以從壓縮請(qǐng)求的大小和減少請(qǐng)求的數(shù)量?jī)煞矫嬷诌M(jìn)行優(yōu)化,主要的優(yōu)化手段如下:

1.2協(xié)議維度

從1.0的短連接到1.1的長(zhǎng)連接到HTTP2.0、3.0,做出了很多改變,每次協(xié)議的升級(jí)對(duì)前端性能優(yōu)化來(lái)講都是一次飛躍。HTTP2.0的新特性如下:

二進(jìn)制分幀:HTTP2.0在應(yīng)用層與傳輸層之間增加一個(gè)二進(jìn)制分幀層,將所有傳輸?shù)男畔⒎指顬楦〉南⒑蛶?并對(duì)它們采用二進(jìn)制格式的編碼,使通信都在一個(gè)可以承載任意數(shù)量的雙向數(shù)據(jù)流的TCP連接上完成。

壓縮頭部:使用HPACK算法,規(guī)定了在客戶端和服務(wù)器端會(huì)使用并且維護(hù)“首部表”來(lái)跟蹤和存儲(chǔ)之前發(fā)送的鍵值對(duì),對(duì)于相同的頭部,不必再通過(guò)請(qǐng)求發(fā)送,減少了頭部開(kāi)銷(xiāo)。

多路復(fù)用:客戶端和服務(wù)器可以把HTTP消息分解為互不依賴的幀,然后亂序發(fā)送,最后再在另一端把它們重新組合起來(lái)。

請(qǐng)求優(yōu)先級(jí):每個(gè)流都可以帶有一個(gè)31bit的優(yōu)先值:0表示最高優(yōu)先級(jí);2的31次方-1表示最低優(yōu)先級(jí)。

服務(wù)器推送:通過(guò)提供push-promise幀來(lái)實(shí)現(xiàn)真正意義上的瀏覽器推送,擺脫利用ajax輪詢進(jìn)行偽實(shí)時(shí)的場(chǎng)景。

2、瀏覽器渲染

2.1 瀏覽器單線程解析渲染阻塞

瀏覽器的主要構(gòu)成如下:

它的幾個(gè)常駐線程如下:

由于GUI線程和JS引擎線程互斥,故衍生了一系列避免渲染過(guò)程中發(fā)生阻塞的優(yōu)化方法,如樣式文件放頭部,腳本文件放在DOM節(jié)點(diǎn)最末尾;針對(duì)不需要操作DOM的腳本,可以采用動(dòng)態(tài)創(chuàng)建script標(biāo)簽的方式載入;腳本文件加上async或者defer等。

2.2 巨大的DOM開(kāi)銷(xiāo)

在瀏覽器渲染的過(guò)程中,巨大的DOM開(kāi)銷(xiāo)無(wú)疑成為了渲染效率是最大瓶頸。通過(guò)如下代碼可以輸出一個(gè)空DOM節(jié)點(diǎn),查看它所包含的300余個(gè)屬性和事件。 

  1. let ele = document.createElement("div")  
  2. let obj = {}  
  3. for (const prop in ele) {  
  4.   obj[prop]=ele[prop]  
  5.  
  6. console.log(obj) 

2.2.1重繪與回流

重繪是指當(dāng)頁(yè)面展示元素中的一些元素需要更新屬性,這些屬性只是影響元素的外觀、風(fēng)格,而不會(huì)影響布局的,比如background-color?;亓魇侵府?dāng)頁(yè)面展示元素中的一部分(或全部)因?yàn)樵氐囊?guī)模尺寸、布局、隱藏等改變而需要重新構(gòu)建。顯然,重繪不一定導(dǎo)致回流,回流必然導(dǎo)致重繪。

它們都會(huì)帶來(lái)一定的DOM開(kāi)銷(xiāo),需要盡力去避免,常見(jiàn)的避免手段有避免觸發(fā)同步布局事件;對(duì)于復(fù)雜動(dòng)畫(huà)效果,使用絕對(duì)定位讓其脫離文檔流;css3硬件加速(transform、opacity、filters、Will-change)等。

2.2.2虛擬DOM

針對(duì)巨大的DOM開(kāi)銷(xiāo),除了盡力避免重繪和回流,近幾年還有一種比較流行的,各大框架比如VUE、react都使用的虛擬DOM的方式。

虛擬DOM是一顆以js對(duì)象為基礎(chǔ)的樹(shù),用對(duì)象屬性來(lái)描述節(jié)點(diǎn),是對(duì)DOM的抽象,通過(guò)一系列操作將其映射到真實(shí)環(huán)境。

用一段代碼來(lái)模擬展示一下這個(gè)過(guò)程,首先用戶編寫(xiě)模板如下: 

  1. <ul id="myId">  
  2.  <li v-for="item in list">{{item}}</li>  
  3. </ul> 

編譯后的內(nèi)容如下所示,采用了creatElement語(yǔ)法糖的形式創(chuàng)建節(jié)點(diǎn)。 

  1. createElement {  
  2.  "ul",  
  3.  {  
  4.   attr:{  
  5.    id: "myId"  
  6.   }  
  7.  },  
  8.  [  
  9.   createElement("li", 1), 
  10.   createElement("li", 2), 
  11.   createElement("li", 3)  
  12.  ]  

經(jīng)過(guò)渲染函數(shù)的執(zhí)行生成虛擬DOM樹(shù),其大致結(jié)構(gòu)如下:

最終將虛擬DOM樹(shù)轉(zhuǎn)化為真實(shí)DOM。

虛擬DOM對(duì)性能的DOM開(kāi)銷(xiāo)的優(yōu)化主要體現(xiàn)在當(dāng)節(jié)點(diǎn)有變化時(shí),它可以通過(guò)differ算法比較變化前后的虛擬DOM結(jié)構(gòu)的變化,通過(guò)對(duì)節(jié)點(diǎn)屬性的修改做必要的調(diào)整,而不是無(wú)腦的銷(xiāo)毀舊節(jié)點(diǎn)創(chuàng)建新節(jié)點(diǎn)。

這個(gè)過(guò)程的主要步驟是:用js對(duì)象結(jié)構(gòu)表示DOM樹(shù)的結(jié)構(gòu),然后用這個(gè)樹(shù)構(gòu)造一個(gè)真正的DOM樹(shù),插入文檔中;當(dāng)狀態(tài)變更時(shí),重新構(gòu)造一棵新樹(shù),與舊樹(shù)進(jìn)行對(duì)比,記錄差異;將記錄的差異應(yīng)用到所構(gòu)建的真正的DOM樹(shù)上。需要特別注意的是,differ算法遵循同級(jí)比較的原則,在使用的過(guò)程中要盡量減少跨層級(jí)的DOM調(diào)整。 

 

責(zé)任編輯:龐桂玉 來(lái)源: 前端大全
相關(guān)推薦

2025-02-25 12:00:00

Java線程開(kāi)發(fā)

2019-11-01 14:00:58

前端性能優(yōu)化代碼

2020-10-16 09:00:12

前端開(kāi)發(fā)技術(shù)

2020-10-16 10:40:39

前端性能可視化

2022-11-16 12:03:13

性能優(yōu)化前端

2022-05-17 09:02:30

前端性能優(yōu)化

2021-07-05 14:55:28

前端優(yōu)化圖片

2020-07-17 19:55:50

Vue前端性能優(yōu)化

2022-03-02 11:13:50

Web前端開(kāi)發(fā)

2023-04-10 11:18:38

前端性能優(yōu)化

2012-01-10 16:22:25

Web

2020-03-09 16:43:06

腳本語(yǔ)言瀏覽器JavaScript

2013-01-22 15:27:23

WebWeb前端

2022-01-09 16:45:36

前端性能優(yōu)化編程

2021-09-18 10:07:23

開(kāi)發(fā)技能代碼

2022-09-13 12:56:28

前端優(yōu)化

2023-12-14 17:21:28

前端性能優(yōu)化

2020-08-24 07:12:17

前端CRP性能優(yōu)化

2020-03-31 14:16:25

前端性能優(yōu)化HTTP

2019-07-29 10:39:39

前端性能優(yōu)化緩存
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)