前言
以下內容是個人的一些學習總結,如有不對,歡迎大佬指正。
一.真實DOM和渲染流程
在開始虛擬DOM之前,讓我們先來了解一下真實的DOM以及瀏覽器是怎麼進行解析的。瀏覽器渲染引擎工作流程大致分為以下四類:創建DOM樹 -> 生成render樹 -> 布局render樹 -> 繪製render樹
- 創建DOM樹:解析HTML生成DOM樹 - 渲染引擎首先解析HTML文檔,生成DOM樹。 用CSS分析器,分析CSS文件和元素上的inline樣式,生成頁面的樣式表。
- 生成render樹:將DOM樹和樣式表,關聯起來,構建一顆Render(渲染)樹
- 布局render樹:有了Render樹,瀏覽器開始對渲染樹的每個節點進行布局處理,確定其在螢幕上的顯示位置。
- 繪製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算法。
寫在最後
如果文章有錯誤或者不妥之處,歡迎大家指正,謝謝。