虛擬DOM :virtual dom(以下簡稱vdom,是vue和react的核心),使用比較簡單。 一,vdom是什麼,為何會存在vdom 1,什麼是vdom:用js模擬DOM結構,DOM操作非常‘昂貴’,DOM變化的對比,放在JS層來做(圖靈完備語言),提高重繪性能 需求:根據給出的數據,將該數 ...
虛擬DOM :virtual dom(以下簡稱vdom,是vue和react的核心),使用比較簡單。
一,vdom是什麼,為何會存在vdom
1,什麼是vdom:用js模擬DOM結構,DOM操作非常‘昂貴’,DOM變化的對比,放在JS層來做(圖靈完備語言),提高重繪性能
需求:根據給出的數據,將該數據展示成一個表格, 隨便修改一個信息, 表格也跟著修改,下麵使用jquery實現demo:
<div id="container"></div> <button id="btn-change">change</button> <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script> <script> var data = [{ name: '張三', age: '20', address: '北京' }, { name: '李四', age: '21', address: '上海' }, { name: '王五', age: '22', address: '廣州' }]; // 渲染函數 function render(data) { var $container = $('#container'); // 清空容器,重要 $container.html(''); // 拼接table var $table = $('<table>'); $table.append($('<tr><td>name</td><td>age</td><td>address</td></tr>')); data.forEach(function(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 = '深圳'; // re-render 再次渲染 render(data) }) // 頁面載入完立刻執行(初次渲染) render(data)
遇到的問題:DOM操作是昂貴的,改動後,整個container容器都重新渲染了一遍,相當於‘推倒重來’,如果項目複雜,非常影響性能
dom操作的屬性是非常多的,非常複雜,操作很昂貴,所以,儘量用js代替操作,例:
var div = document.createElement('div'); var item, result = ''; for(item in div) { result += '|' + item; } console.log(result);
vdom可以解決這個問題
二,vdom如何應用,核心API是什麼
1,介紹snabbdom
var vnode = h('ul#list', {}, [
h('li.item', {}, 'Item 1'),
h('li.item', {}, 'Item 2')
])
{
tag: 'ul',
attrs: {
id: 'list'
},
children: [{
tag: 'li',
attrs: { className: 'item' },
children: ['Item 1']
}, {
tag: 'li',
attrs: { className: 'item' },
children: ['Item 2']
}]
}
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <div id="container"></div> <button id="btn-change">change</button> <script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom.js"></script> <script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-class.js"></script> <script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-props.js"></script> <script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-style.js"></script> <script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-eventlisteners.min.js"></script> <script src="https://cdn.bootcss.com/snabbdom/0.7.1/h.js"></script> <script> var snabbdom = window.snabbdom; // 定義patch var patch = snabbdom.init([ snabbdom_class, snabbdom_props, snabbdom_style, snabbdom_eventlisteners ]) // 定義h var h = snabbdom.h; var container = document.getElementById('container'); // 生成vnode var vnode = h('ul#list', {}, [ h('li.item', {}, 'Item 1'), h('li.item', {}, 'Item 2'), ]); patch(container,vnode) // 模擬改變 var btnChange = document.getElementById('btn-change'); btnChange.addEventListener('click', function() { var newVnode = h('ul#list', {}, [ h('li.item', {}, 'Item 1'), h('li.item', {}, 'Item 222'), h('li.item', {}, 'Item 333'), ]); patch(vnode,newVnode); }) </script> </body> </html>
2,重做之前的demo
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <div id="container"></div> <button id="btn-change">change</button> <script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom.js"></script> <script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-class.js"></script> <script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-props.js"></script> <script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-style.js"></script> <script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-eventlisteners.min.js"></script> <script src="https://cdn.bootcss.com/snabbdom/0.7.1/h.js"></script> <script> var snabbdom = window.snabbdom; // 定義關鍵函數patch var patch = snabbdom.init([ snabbdom_class, snabbdom_props, snabbdom_style, snabbdom_eventlisteners ]) // 定義關鍵函數 h var h = snabbdom.h; // 原始數據 var data = [{ name: '張三', age: '20', address: '北京' }, { name: '李四', age: '21', address: '上海' }, { name: '王五', age: '22', address: '廣州' }]; // 把表頭也放在data中 data.unshift({ name: '姓名', age: '年齡', address: '地址', }) var container = document.getElementById('container'); var vnode; function render(data) { var newVnode = h('table',{},data.map(function(item){ var tds = []; var i; for(i in item) { if(item.hasOwnProperty(i)) { tds.push(h('td',{},item[i] + '')) } } return h('tr',{},tds) })) if(vnode) { // re-render patch(vnode,newVnode) } else { // 初次渲染 patch(container,newVnode) } // 存儲當前vnode結果 vnode = newVnode; } // 初次渲染 render(data) var btnChange = document.getElementById('btn-change'); btnChange.addEventListener('click', function() { data[1].age = 30; data[2].address = '深圳'; // re-render render(data) })
</script> </body> </html>
3,核心API
h('標簽名',{...屬性...},[...子元素...]) //多個子元素
h('標簽名',{...屬性...},'...') //只有一個子元素
patch(container,vnode) //初次渲染,會把外層容器替代掉
patch(vnode,newVnode) //re-render
三,介紹diff演算法(vdom核心演算法)
1,vdom為何用diff演算法
diff 是linux的基礎命令,可以比較兩個文本文件的不同 git diff xxx; vdom中應用diff演算法是為了找出需要更新的節點
比如新建兩個文本文件,log1.txt log2.txt
diff log1.txt log2.txt
diff線上對比:http://tool.oschina.net/diff
使用vdom原因:DOM操作是昂貴的,因此儘量減少DOM操作
找出本次DOM必須更新的節點來更新,其他的不更新
這個找出的過程,就需要diff演算法 找出前後兩個vdom的差異
2,diff演算法的實現流程
vdom核心函數:h生成dom節點,patch函數-進行對比和渲染的
patch(container,vnode) 初次渲染,會把外層容器替代掉
patch(vnode,newVnode) re-render
3,如何用vnode生成真是的dom節點
diff實現:
1,patch(container,vnode)
2,patch(vnode,newVnode)
核心邏輯:createElement 和 updateChildren
// patch(container,vnode) 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(childNode) { // 遞歸調用 createElement 給elem添加子元素 elem.appendChild(createElement(childVnode)); //遞歸 }) // 返回真實的DOM元素 return elem; } // patch(vnode,newVnode) function updateChildren(vnode, newVnode) { var children = vnode.children || []; var newChildren = newVnode.children || []; // 遍歷現有的children children.forEach(function(child, index) { var newChild = newChildren[index]; if (newChild == null) { return; } if (child.tag === newChild.tag) { // 兩者tag一樣 深層次對比 updateChildren(child, newChild); } else { // 兩者tag不一樣 替換 replaceNode(child, newChild) } }) } function replaceNode(vnode, newVnode) { var elem = vnode.elem; //真實的DOM節點 var newElem = createElement(newVnode); // 替換 }