vdom是虛擬DOM(Virtual DOM)的簡稱,指的是用JS模擬的DOM結構,將DOM變化的對比放在JS層來作。換而言之,vdom就是JS對象。javascript
以下DOM結構:css
<ul id="list">
<li class="item">Item1</li>
<li class="item">Item2</li>
</ul>
複製代碼
映射成虛擬DOM就是這樣:html
{
tag: "ul",
attrs: {
id: "list"
},
children: [
{
tag: "li",
attrs: { className: "item" },
children: ["Item1"]
}, {
tag: "li",
attrs: { className: "item" },
children: ["Item2"]
}
]
}
複製代碼
如今有一個場景,實現如下需求:前端
[
{
name: "張三",
age: "20",
address: "北京"
},
{
name: "李四",
age: "21",
address: "武漢"
},
{
name: "王五",
age: "22",
address: "杭州"
},
]
複製代碼
將該數據展現成一個表格,而且隨便修改一個信息,表格也跟着修改。 用jQuery實現以下:vue
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="container"></div>
<button id="btn-change">改變</button>
<script src="https://cdn.bootcss.com/jquery/3.2.0/jquery.js"></script>
<script> const data = [{ name: "張三", age: "20", address: "北京" }, { name: "李四", age: "21", address: "武漢" }, { name: "王五", age: "22", address: "杭州" }, ]; //渲染函數 function render(data) { const $container = $('#container'); $container.html(''); const $table = $('<table>'); // 重繪一次 $table.append($('<tr><td>name</td><td>age</td><td>address</td></tr>')); data.forEach(item => { //每次進入都重繪 $table.append($(`<tr><td>${item.name}</td><td>${item.age}</td><td>${item.address}</td></tr>`)) }) $container.append($table); } $('#btn-change').click(function () { data[1].age = 30; data[2].address = '深圳'; render(data); }); </script>
</body>
</html>
複製代碼
這樣點擊按鈕,會有相應的視圖變化,可是你審查如下元素,每次改動以後,table標籤都得從新建立,也就是說table下面的每個欄目,不論是數據是否和原來同樣,都得從新渲染,這並非理想中的狀況,當其中的一欄數據和原來同樣,咱們但願這一欄不要從新渲染,由於DOM重繪至關消耗瀏覽器性能。java
所以咱們採用JS對象模擬的方法,將DOM的比對操做放在JS層,減小瀏覽器沒必要要的重繪,提升效率。node
固然有人說虛擬DOM並不比真實的DOM快,其實也是有道理的。當上述table中的每一條數據都改變時,顯然真實的DOM操做更快,由於虛擬DOM還存在js中diff算法的比對過程。因此,上述性能優點僅僅適用於大量數據的渲染而且改變的數據只是一小部分的狀況。react
虛擬DOM更加優秀的地方在於:jquery
一、它打開了函數式的UI編程的大門,即UI = f(data)這種構建UI的方式。linux
二、能夠將JS對象渲染到瀏覽器DOM之外的環境中,也就是支持了跨平臺開發,好比ReactNative。
另外你們能夠參考尤大的一些回答: www.zhihu.com/question/31…
snabbdom地址:github.com/snabbdom/sn…
這是一個簡易的實現vdom功能的庫,相比vue、react,對於vdom這塊更加簡易,適合咱們學習vdom。vdom裏面有兩個核心的api,一個是h函數,一個是patch函數,前者用來生成vdom對象,後者的功能在於作虛擬dom的比對和將vdom掛載到真實DOM上。
簡單介紹一下這兩個函數的用法:
如今咱們就來用snabbdom重寫一下剛纔的例子:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="container"></div>
<button id="btn-change">改變</button>
<script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-class.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-props.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-style.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-eventlisteners.min.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.3/h.js"></script>
<script> let snabbdom = window.snabbdom; // 定義patch let patch = snabbdom.init([ snabbdom_class, snabbdom_props, snabbdom_style, snabbdom_eventlisteners ]); //定義h let h = snabbdom.h; const data = [{ name: "張三", age: "20", address: "北京" }, { name: "李四", age: "21", address: "武漢" }, { name: "王五", age: "22", address: "杭州" }, ]; data.unshift({name: "姓名", age: "年齡", address: "地址"}); let container = document.getElementById('container'); let vnode; const render = (data) => { let newVnode = h('table', {}, data.map(item => { let tds = []; for(let i in item) { if(item.hasOwnProperty(i)) { tds.push(h('td', {}, item[i] + '')); } } return h('tr', {}, tds); })); if(vnode) { patch(vnode, newVnode); } else { patch(container, newVnode); } vnode = newVnode; } render(data); let btnChnage = document.getElementById('btn-change'); btnChnage.addEventListener('click', function() { data[1].age = 30; data[2].address = "深圳"; //re-render render(data); }) </script>
</body>
</html>
複製代碼
再進入頁面:
你會發現,只有改變的欄目才閃爍,也就是進行重繪,數據沒有改變的欄目仍是保持原樣,這樣就大大節省了瀏覽器從新渲染的開銷。所謂diff算法,就是用來找出兩段文本之間的差別的一種算法。
做爲一個前端,你們常常會聽到diff算法這個詞,其實diff並非前端原創的算法,其實這一個算法早已在linux的diff命令中有所體現,而且你們經常使用的git diff也是運用的diff算法。
DOM操做是很是昂貴的,所以咱們須要儘可能地減小DOM操做。這就須要找出本次DOM必須更新的節點來更新,其餘的不更新,這個找出的過程,就須要應用diff算法。
如下代碼只是幫助你們理解diff算法的原理和流程,不可用於生產環境。
將vdom轉化爲真實dom:
const createElement = (vnode) => {
let tag = vnode.tag;
let attrs = vnode.attrs || {};
let children = vnode.children || [];
if(!tag) {
return null;
}
//建立元素
let elem = document.createElement(tag);
//屬性
let attrName;
for (attrName in attrs) {
if(attrs.hasOwnProperty(attrName)) {
elem.setAttribute(attrName, attrs[attrName]);
}
}
//子元素
children.forEach(childVnode => {
//給elem添加子元素
elem.appendChild(createElement(childVnode));
})
//返回真實的dom元素
return elem;
}
複製代碼
用簡易diff算法作更新操做:
function updateChildren(vnode, newVnode) {
let children = vnode.children || [];
let newChildren = newVnode.children || [];
children.forEach((childVnode, index) => {
let newChildVNode = newChildren[index];
if(childVnode.tag === newChildVNode.tag) {
//深層次對比, 遞歸過程
updateChildren(childVnode, newChildVNode);
} else {
//替換
replaceNode(childVnode, newChildVNode);
}
})
}
複製代碼
參考資料: