當前位置:
首頁 >
前端技术
> javascript
>内容正文
javascript
JavaScript从初级往高级走系列————Virtual Dom
生活随笔
收集整理的這篇文章主要介紹了
JavaScript从初级往高级走系列————Virtual Dom
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
原文博客地址:https://finget.github.io/2018/05/22/virtualDom/
什么是虛擬DOM
- 用JS模擬DOM結構
- DOM變化的對比,放在JS層來做(圖靈完備語言)
- 提高重繪性能
重繪和回流
頁面渲染過程:
- 當render tree中的一部分(或全部)因為元素的規(guī)模尺寸,布局,隱藏等改變而需要重新構建。這就稱為回流(reflow)。
- 當render tree中的一些元素需要更新屬性,而這些屬性只是影響元素的外觀,風格,而不會影響布局的,比如background-color。則就叫稱為重繪。
模擬虛擬DOM
<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: '上海'}]// 渲染函數(shù)function render(data) {var $container = $('#container');// 清空容器,重要!!!$container.html('');// 拼接 tablevar $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);})// 頁面加載完立刻執(zhí)行(初次渲染)render(data);</script> </body> </html>雖然只改變了兩個數(shù)據(jù),但是整個table都閃爍了(回流&重繪)
- DOM操作是‘昂貴’的,js運行效率高
- 盡量減少DOM操作,盡量減少回流重繪
虛擬DOM如何應用,核心API是什么
介紹 snabbdom
snabbdom GitHub地址
官網(wǎng)例子:
var snabbdom = require('snabbdom'); var patch = snabbdom.init([ // Init patch function with chosen modulesrequire('snabbdom/modules/class').default, // makes it easy to toggle classesrequire('snabbdom/modules/props').default, // for setting properties on DOM elementsrequire('snabbdom/modules/style').default, // handles styling on elements with support for animationsrequire('snabbdom/modules/eventlisteners').default, // attaches event listeners ]); var h = require('snabbdom/h').default; // helper function for creating vnodesvar container = document.getElementById('container');// h函數(shù)生成一個虛擬節(jié)點 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中// 數(shù)據(jù)改變,重新生成一個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 statesnabbdom h 函數(shù)
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']}] }snabbdom patch 函數(shù)
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); })snabbdom例子
<!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;// 定義 patchvar patch = snabbdom.init([snabbdom_class,snabbdom_props,snabbdom_style,snabbdom_eventlisteners])// 定義 hvar h = snabbdom.h;var container = document.getElementById('container');// 生成 vnodevar vnode = h('ul#list',{},[h('li.item',{},'Item 1'),h('li.item',{},'Item 2')])patch(container, vnode);// 模擬數(shù)據(jù)改變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>看圖,只有修改了的數(shù)據(jù)才進行了刷新,減少了DOM操作,這其實就是vnode與newVnode對比,找出改變了的地方,然后只重新渲染改變的
重做之前的demo
<!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;// 定義關鍵函數(shù) patchvar patch = snabbdom.init([snabbdom_class,snabbdom_props,snabbdom_style,snabbdom_eventlisteners]);// 定義關鍵函數(shù) hvar h = snabbdom.h;// 原始數(shù)據(jù)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')// 渲染函數(shù)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-renderpatch(vnode, newVnode);} else {// 初次渲染patch(container, newVnode);}// 存儲當前的 vnode 結果vnode = newVnode;}// 初次渲染render(data)var btnChange = document.getElementById('btn-change')btnChange.addEventListener('click', function () {data[1].age = 30data[2].address = '深圳'// re-renderrender(data)})</script> </body> </html>
核心API
- h('<標簽名>',{...屬性...},[...子元素...])
- h('<標簽名>',{...屬性...},'...')
- patch(container,vnode)
- patch(vnode,newVnode)
簡單介紹 diff 算法
什么是 diff 算法
這里有兩個文本文件:
借用git bash中 diff 命令可以比較兩個文件的區(qū)別:
在線diff工具
虛擬DOM ---> DOM
// 一個實現(xiàn)流程,實際情況還很復雜 function createElement(vnode) {var tag = vnode.tag // 'ul'var attrs = vnode.attrs || {}var children = vnode.children || []if (!tag) {return null}// 創(chuàng)建真實的 DOM 元素var elem = document.createElement(tag)// 屬性var attrNamefor (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 ---> newVnode
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 節(jié)點var newElem = createElement(newVnode);// 替換 }最后
創(chuàng)建了一個前端學習交流群,感興趣的朋友,一起來嗨呀!
總結
以上是生活随笔為你收集整理的JavaScript从初级往高级走系列————Virtual Dom的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java基础 之软引用、弱引用、虚引用
- 下一篇: 又是绩效考核时,KPI 和 OKR 到底