淺談Vue中的虛擬DOM

前言

  Vue2.0引入了虛擬DOM,比Vue1.0的初始渲染速度提高了2~4倍,並大大下降了內存消耗。目前主流的前端框架Vue、React核心技術也都使用了虛擬DOM,你是否好奇爲何要提出虛擬DOM,虛擬DOM是什麼,它有什麼優點?這一切的問題,都將在本篇揭曉。前端

爲何要提出虛擬DOM

  在Web早期,頁面的交互比較簡單,沒有複雜的狀態須要管理,也不太須要頻繁的操做DOM,隨着時代的發展,頁面上的功能愈來愈多,咱們須要實現的需求也愈來愈複雜,DOM的操做也愈來愈頻繁。經過js操做DOM的代價很高,由於會引發頁面的重排重繪,增長瀏覽器的性能開銷,下降頁面渲染速度,既然操做DOM的代價很高那麼有沒有那種方式能夠減小對DOM的操做?這就是爲何提出虛擬DOM一個很重要的緣由。node

Virtual DOM 是什麼?

  Vue.js經過編譯將模版轉換成渲染函數(render),執行渲染函數就能夠獲得一個以vnode節點(JavaScript對象)做爲基礎的樹形結構,vnode節點裏麪包含標籤名(tag)、屬性(attrs)和子元素對象(children)等等屬性,這個樹形結構就是Virtual DOM,簡單來講,能夠把Virtual DOM理解爲一個樹形結構的JS對象。git

模板轉換成視圖的過程整個過程: github

image.png
咱們先對上圖幾個概念嵌入解釋:

  • 渲染函數:渲染函數是用來生成虛擬DOM的。Vue推薦使用模版來構建咱們的應用界面,在實現中Vue佈局模版編譯成渲染函數,固然咱們也能夠不寫模版,直接寫渲染函數,這樣子更接近編譯後的模版。
  • vnode虛擬節點:它能夠表明一個真實的DOM節點經過createElement方法能將vnode渲染成DOM節點,簡單地說,虛擬節點能夠理解成節點描述對象,它描述了應該怎樣去建立真實的DOM節點。
  • patch(也稱爲patching算法):虛擬DOM最核心的部分,它能夠將vnode渲染成真實的DOM,這個過程是對比新舊虛擬節點之間有哪些不一樣,而後根據對比結果找出須要更新的的節點進行更新。

對於虛擬DOM,我們來看一個簡單的實例,就是下圖所示的這個,詳細的闡述了模板 → 渲染函數 → 虛擬DOM樹 → 真實DOM的一個過程 算法

image.png

爲何虛擬DOM能夠提升渲染速度

  傳統方式用js操做DOM會有不少額外的DOM操做,例如,一個ul標籤下有不少個li標籤,其中只有一個li有變化,這種狀況下若是使用新的ul去替代舊的ul,其實除了那個發生變化的li節點以外,其餘節點都不須要從新渲染。因爲DOM操做比較慢,因此這些DOM操做在性能上會有必定的浪費,避免這些沒必要要的DOM操做會提高很大一部分性能(減小重排重繪從而節省瀏覽器的性能開銷)。瀏覽器

  爲了不沒必要要的DOM操做,虛擬DOM在虛擬節點映射到視圖的過程當中,將虛擬節點與上一次渲染視圖緩存的虛擬節點(oldVnode)作對比,找出真正須要更新的節點來進行DOM操做,從而避免操做其餘無任何改動的DOM。 其實虛擬DOM在Vue.js中主要作了兩件事:緩存

  • 提供與真實DOM節點所對應的虛擬節點vnode
  • 將虛擬節點vnode和舊虛擬節點oldVnode進行比對,而後更新視圖 對兩個虛擬節點進行對比是虛擬DOM中最核心的算法即patch,patch算法的核心是diff算法,它能夠判斷出哪些節點發生了變化,從而只對發生了變化的節點進行更新操做。

diff算法

image.png
Vue的diff算法是基於snabbdom改造過來的,僅在同級的vnode間作diff,遞歸地進行同級vnode的diff,最終實現整個DOM樹的更新。由於跨層級的操做是很是少的,忽略不計,這樣時間複雜度就從O(n3)變成O(n)。

diff 算法包括幾個步驟:bash

  • 用 JavaScript 對象結構表示 DOM 樹的結構;而後用這個樹構建一個真正的 DOM 樹,插到文檔當中
  • 當狀態變動的時候,從新構造一棵新的對象樹。而後用新的樹和舊的樹進行比較,記錄兩棵樹差別
  • 把所記錄的差別應用到所構建的真正的DOM樹上,視圖就更新了
    image.png

diff算法的實現過程

diff 算法自己很是複雜,實現難度很大。本文去繁就簡,粗略介紹如下兩個核心函數實現流程:前端框架

  • patch(container,vnode) :初次渲染的時候,將vnode渲染成真正的DOM而後插入到容器裏面。
  • patch(vnode,newVnode):再次渲染的時候,將新的vnode和舊的vnode相對比,而後之間差別應用到所構建的真正的DOM樹上。

patch(container,vnode)

經過這個函數可讓vnode渲染成真正的DOM,咱們經過如下模擬代碼,能夠了解大體過程:app

function createElement(vnode) {    
var tag = vnode.tag  
var attrs = vnode.attrs || {}    
var children = vnode.children || []    
if (!tag) {       
 return null  
  }    
// 建立真實的 DOM 元素    
var elem = document.createElement(tag)   
 // 屬性    
var attrName    
for (attrName in attrs) {    
    if (attrs.hasOwnProperty(attrName)) { 
           // 給 elem 添加屬性
           elem.setAttribute(attrName, attrs[attrName])
        }
    }
    // 子元素
    children.forEach(function (childVnode) {
        // 給 elem 添加子元素,若是還有子節點,則遞歸的生成子節點。
        elem.appendChild(createElement(childVnode))  // 遞歸
    })    // 返回真實的 DOM 元素   
 return elem
}
複製代碼

patch(vnode,newVnode)

這裏咱們只考慮vnode與newVnode如何對比的狀況:

function updateChildren(vnode, newVnode) {
    var children = vnode.children || []
    var newChildren = newVnode.children || []
  // 遍歷現有的children
    children.forEach(function (childVnode, index) {
        var newChildVnode = newChildren[index]
  // 二者tag同樣
        if (childVnode.tag === newChildVnode.tag) {
            // 深層次對比,遞歸
            updateChildren(childVnode, newChildVnode)
        } else { 
  // 二者tag不同
           replaceNode(childVnode, newChildVnode) 
       }
    }
)}
複製代碼

Virtual DOM的優點

  • 具有跨平臺的優點 因爲 Virtual DOM 是以 JavaScript 對象爲基礎而不依賴真實平臺環境,因此使它具備了跨平臺的能力,好比說瀏覽器平臺、Weex、Node 等。

  • 操做 DOM 慢,js運行效率高。咱們能夠將DOM對比操做放在JS層,提升效率。 由於DOM操做的執行速度遠不如Javascript的運算速度快,所以,把大量的DOM操做搬運到Javascript中,運用patching算法來計算出真正須要更新的節點,最大限度地減小DOM操做,從而顯著提升性能。

  • 提高渲染性能 Virtual DOM的優點不在於單次的操做,而是在大量、頻繁的數據更新下,可以對視圖進行合理、高效的更新。

總結:Vue.js經過編譯將模版轉換成渲染函數(render),執行渲染函數就能夠獲得一個虛擬節點樹(虛擬DOM),虛擬節點樹(虛擬DOM)提供虛擬節點vnode和對新舊兩個vnode進行比對並根據比對結果進行DOM操做來更新視圖,最大限度減小對DOM操做,從而減小瀏覽器的開銷,提升渲染速度,改善用戶體驗。

參考文章: github.com/ljianshu/Bl…

book.douban.com/subject/325…

相關文章
相關標籤/搜索