Vue之虛擬DOM(vdom)

echa攻城獅 發佈 2020-03-18T03:41:25+00:00

瀏覽器渲染引擎工作流程大致分為以下四類:創建DOM樹-> 生成render樹 -> 布局render樹 -> 繪製render樹創建DOM樹:解析HTML生成DOM樹 - 渲染引擎首先解析HTML文檔,生成DOM樹。



前言

以下內容是個人的一些學習總結,如有不對,歡迎大佬指正。

一.真實DOM和渲染流程

在開始虛擬DOM之前,讓我們先來了解一下真實的DOM以及瀏覽器是怎麼進行解析的。瀏覽器渲染引擎工作流程大致分為以下四類:創建DOM樹 -> 生成render樹 -> 布局render樹 -> 繪製render樹

  1. 創建DOM樹:解析HTML生成DOM樹 - 渲染引擎首先解析HTML文檔,生成DOM樹。 用CSS分析器,分析CSS文件和元素上的inline樣式,生成頁面的樣式表。
  2. 生成render樹:將DOM樹和樣式表,關聯起來,構建一顆Render(渲染)樹
  3. 布局render樹:有了Render樹,瀏覽器開始對渲染樹的每個節點進行布局處理,確定其在螢幕上的顯示位置。
  4. 繪製render樹:遍歷渲染樹並用UI後端層將每一個節點繪製出來

二.虛擬DOM

1.虛擬DOM是什麼?

當用原生js或者jq去操作真實DOM的時候,瀏覽器會從構建DOM樹開始從頭到尾執行一遍流程。當操作次數過多時,之前計算DOM節點坐標值等都是白白浪費的性能,虛擬DOM由此誕生。

2.虛擬DOM有什麼好處?

假設一次操作中有10次更新DOM的動作,虛擬DOM不會立即操作DOM,而是將這10次更新的diff內容保存到本地一個JS對象中,最終將這個JS對象一次性attch到DOM樹上,再進行後續操作,避免大量無謂的計算量。所以,用JS對象模擬DOM節點的好處是,頁面的更新可以先全部反映在虛擬DOM上,操作內存中的JS對象的速度顯然要更快,等更新完成後,再將最終的JS對象映射成真實的DOM,交由瀏覽器去繪製。

三.Vue中的虛擬DOM



  • 渲染函數:渲染函數是用來生成虛擬DOM的。Vue推薦使用模板來構建應用介面,在底層實現中Vue將模板編譯成渲染函數。
  • Vnode虛擬節點:它可以代表一個真實的dom節點。通過createElement方法將vnode節點渲染成dom節點。
  • patch:虛擬DOM最核心的部分,它可以將vnode渲染成真實的DOM,這個過程是對比新舊虛擬節點之間有哪些不同,然後根據對比結果找出需要更新的的節點進行更新

參考文檔:

渲染函數Vue文檔 vue 虛擬dom實現原理文章 patch算法源碼

ps: 筆者能力有限,原諒我patch源碼篇看的不是很懂!在這請尤雨溪大大收下我的膝蓋。

四.模擬Vue虛擬DOM

1.安裝vue-cli腳手架,部署vue環境。

這裡大家可以自行安裝。

2.創建虛擬DOM樹

先在element.js文件中實現如何創建虛擬DOM樹。

// element.js

// 虛擬DOM元素類,用來描述DOM
function Element(type, props, children){
  this.type = type; //節點類型
  this.props = props; //屬性
  this.children = children; //子節點
}
// 創建虛擬DOM
function createElement(type, props, children){
  return new Element(type, props, children);
}
//向外輸出
export {
  Element,
  createElement
}
複製代碼

我們來到App.vue文件中調用createElement方法來創建一個DOM對象。

//App.vue
import {createElement} from './js/element.js'

let V_DOM = createElement('ul',{class:'list'},[
  createElement('li', {class:'item'},['item1']),
  createElement('li', {class:'item'},['item2']),
  createElement('li', {class:'item'},['item3'])
])
//列印虛擬DOM
console.log(V_DOM);
複製代碼

註:因為腳手架里的App.vue的內容沒有刪除,這裡只貼了調用到的代碼。

下面我們來看看瀏覽器里列印出來的虛擬DOM。


三.模擬渲染函數渲染虛擬DOM

// element.js

function render(dom) {
  // 根據type類型來創建對應的元素
  let el = document.createElement(dom.type);
  
  // 再去遍歷props屬性對象,然後給創建的元素el設置屬性
  for (let key in dom.props) {
      // 設置屬性的方法
      el.setAttribute(key, dom.props[key]);
  }
  
  // 遍歷子節點
  // // 如果子節點也是虛擬DOM,遞歸構建DOM節點
  // 不是就代表是文本節點,直接創建
  dom.children.forEach(child => {
      child = (child instanceof Element) ? render(child) : document.createTextNode(child);
      // 添加到對應元素內
      el.appendChild(child);
  });

  return el;
}

//向外輸出
export {
  Element,
  createElement,
  render,
}
複製代碼

回到App.vue我們來調用render函數

import {createElement, render, renderDom} from './js/element.js'

let V_DOM = createElement('ul',{class:'list'},[
  createElement('li', {class:'item'},['item1']),
  createElement('li', {class:'item'},['item2']),
  createElement('li', {class:'item'},['item3'])
])
//列印虛擬DOM
console.log(V_DOM);
var el = render(V_DOM);
console.log(el);
document.body.appendChild(el);
複製代碼

來看看效果圖。

真實的DOM結構就構建出來了,接下來渲染到頁面中。

好了,我們已經實現了模擬虛擬DOM並進行了渲染。而diff算法和patch算法才是虛擬DOM最核心的部分,筆者還在努力學習階段。這裡推薦一篇寶藏文章(https://github.com/livoras/blog/issues/13),這裡比較詳細的解析了虛擬DOM算法。


寫在最後

如果文章有錯誤或者不妥之處,歡迎大家指正,謝謝。

關鍵字: