原文博客地址:https://finget.github.io/2018/05/22/virtualDom/
頁面渲染過程:javascript
<ul id="list"> <li class="item">Item 1</li> <li class="item">Item 2</li> </ul>
// js模擬虛擬DOM { 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> <script src="https://cdn.bootcss.com/jquery/2.2.0/jquery.min.js"></script> </head> <body> <div id="container"></div> <button id="btn-change">change</button> <script> var data = [ {name: '張三',age: '20',address: '北京'}, {name: '王五',age: '22',address: '成都'}, {name: '李四',age: '21',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); </script> </body> </html>
雖然只改變了兩個數據,可是整個table都閃爍了(迴流&重繪)css
官網例子:前端
var snabbdom = require('snabbdom'); var patch = snabbdom.init([ // Init patch function with chosen modules require('snabbdom/modules/class').default, // makes it easy to toggle classes require('snabbdom/modules/props').default, // for setting properties on DOM elements require('snabbdom/modules/style').default, // handles styling on elements with support for animations require('snabbdom/modules/eventlisteners').default, // attaches event listeners ]); var h = require('snabbdom/h').default; // helper function for creating vnodes var container = document.getElementById('container'); // h函數生成一個虛擬節點 var vnode = h('div#container.two.classes', {on: {click: someFn}}, [ h('span', {style: {fontWeight: 'bold'}}, 'This is bold'), ' and this is just normal text', h('a', {props: {href: '/foo'}}, 'I\'ll take you places!') ]); // Patch into empty DOM element – this modifies the DOM as a side effect patch(container, vnode); // 把vnode加入到container中 // 數據改變,從新生成一個newVnode var newVnode = h('div#container.two.classes', {on: {click: anotherEventHandler}}, [ h('span', {style: {fontWeight: 'normal', fontStyle: 'italic'}}, 'This is now italic type'), ' and this is still just normal text', h('a', {props: {href: '/bar'}}, 'I\'ll take you places!') ]); // Second `patch` invocation // 將newVnode更新到以前的vnode中,從而更新視圖 patch(vnode, newVnode); // Snabbdom efficiently updates the old view to the new state
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'] } ] }
var vnode = h('ul#list',{},[ h('li.item',{},'Item 1'), h('li.item',{},'Item 2') ]) var container = document.getElementById('container'); patch(container, vnode); // 模擬改變 var btnChange = document.getElementById('btn-change'); btnChange.addEventListener('click',function(){ var newVnode = h('ul#list',{},[ h('li.item',{},'Item 111'), h('li.item',{},'Item 222'), h('li.item',{},'Item 333') ]) patch(vnode, newVnode); })
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <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.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/h.js"></script> <script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-eventlisteners.js"></script> </head> <body> <div id="container"></div> <button id="btn-change">change</button> <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>
看圖,只有修改了的數據才進行了刷新,減小了DOM操做,這其實就是vnode與newVnode對比,找出改變了的地方,而後只從新渲染改變的java
<!DOCTYPE html> <html> <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.0/snabbdom.js"></script> <script src="https://cdn.bootcss.com/snabbdom/0.7.0/snabbdom-class.js"></script> <script src="https://cdn.bootcss.com/snabbdom/0.7.0/snabbdom-props.js"></script> <script src="https://cdn.bootcss.com/snabbdom/0.7.0/snabbdom-style.js"></script> <script src="https://cdn.bootcss.com/snabbdom/0.7.0/snabbdom-eventlisteners.js"></script> <script src="https://cdn.bootcss.com/snabbdom/0.7.0/h.js"></script> <script type="text/javascript"> 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: '22',address: '成都'}, {name: '李四',age: '21',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>
這裏有兩個文本文件:
借用git bash
中 diff
命令能夠比較兩個文件的區別:node
在線diff工具jquery
虛擬DOM ---> DOMgit
// 一個實現流程,實際狀況還很複雜 function createElement(vnode) { var tag = vnode.tag // 'ul' 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 }
vnode ---> newVnodegithub
function updateChildren(vnode, newVnode) { var children = vnode.children || []; var newChildren = newVnode.children || []; children.forEach(function (childVnode, index) { var newChildVnode = newChildren[index]; if (childVnode.tag === newChildVnode.tag) { // 深層次對比,遞歸 updateChildren(childVnode, newChildVnode); } else { // 替換 replaceNode(childVnode, newChildVnode); } }) } function replaceNode(vnode, newVnode) { var elem = vnode.elem; // 真實的 DOM 節點 var newElem = createElement(newVnode); // 替換 }
建立了一個前端學習交流羣,感興趣的朋友,一塊兒來嗨呀!算法