vdom
vdom
virtual dom,虚拟DOM
用JS模拟DOM结构
DOM变化的对比,放在JS层来做(图灵完备语言)
提高重绘性能
1、知识点一
一个简单的dom结构
<ul id='list'>
<li class='item'>Item 1</li>
<li class='item'>Item 2</li>
</ul>
用js表示上面Dom结构
{
tag:'ul',
attrs: {
id:'list'
},
childern:[
{
tag:'li',
attrs:{className:'item'}
children:['Item 1']
},
{
tag:'li',
attrs:{className:'item'}
children:['Item 2']
}
]
}
2、知识点二
设计一个需求场景
将该数据展示成一个表
随便修改一个信息,表格也跟着修改
[
{
name:'张三',
age:'20',
address:'北京'
},
{
name:'李四',
age:'21',
address:'上海'
},
{
name:'王五',
age:'22',
address:'广州'
}
]
用jQuery实现上面场景
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="container"></div>
<button id="btn-change">change</button>
<script type="text/javascript" src="https://cdn.bootcss.com/jquery/3.2.0/jquery.js"></script>
<script type="text/javascript">
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)
</script>
</body>
</html>
3、知识点三
遇到的问题浏览器为div节点创建了很多的属性,修改DOM是昂贵的
var div=document.createElement('div');
var item,result=''
for(item in div){
result +=' | '+item;
}
console.log(result);
打印出来的log
DOM 操作是“昂贵”的,js 运行效率高
尽量减少 DOM 操作,而不是“推倒重来”
项目越复杂,影响就越严重
vdom 即可解决这个问题
snabbdom
介绍snabbdom
重做之前的demo
核心API
1、知识点一
用snabbdom模拟上面的vdom
{
tag:'ul',
attrs: {
id:'list'
},
childern:[
{
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 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');
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);
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
patch(vnode, newVnode); // Snabbdom efficiently updates the old view to the new state
snabbdom的patch函数
2、知识点二
简单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.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.js"> </script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.1/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 container = document.getElementById('container')
// 生成 vnode
var vnode = h('ul#list', {}, [
h('li.item', {}, 'Item 1'),
h('li.item', {}, 'Item 2')
])
patch(container, vnode)
document.getElementById('btn-change').addEventListener('click', function () {
// 生成 newVnode
var newVnode = h('ul#list', {}, [
h('li.item', {}, 'Item 1'),
h('li.item', {}, 'Item B'),
h('li.item', {}, 'Item 3')
])
patch(vnode, newVnode)
})
</script>
</body>
</html>
重做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
// 定义关键函数 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、知识点三
snabbdom的h函数两种用法
h(‘<标签名>’,{…属性…},[…子元素…])
h(‘<标签名>’,{…属性…},‘….’)
snabbdom的patch函数两种用法
patch(container,vnode)
patch(vnode, newVnode)
diff算法
1、知识点一
vdom 为何使用 diff 算法
DOM 操作是“昂贵”的,因此尽量减少 DOM 操作
找出本次 DOM 必须更新的节点来更新,其他的不更新
这个“找出”的过程,就需要 diff 算法
patch(vnode, newVnode)
vnode内容
{
tag:'ul',
attrs: {
id:'list'
},
childern:[
{
tag:'li',
attrs:{className:'item'}
children:['Item 1']
},
{
tag:'li',
attrs:{className:'item'}
children:['Item 2']
}
]
}
newVnode内容
{
tag:'ul',
attrs: {
id:'list'
},
childern:[
{
tag:'li',
attrs:{className:'item'}
children:['Item 1']
},
{
tag:'li',
attrs:{className:'item'}
children:['Item 222']
},
{
tag:'li',
attrs:{className:'item'}
children:['Item 3']
}
]
}
2、知识点二
updateChildren函数
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)
// 替换
}
createElement函数
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
}
3、知识点三
diff 实现过程
patch(container, vnode) 和 patch(vnode, newVnode)
createElment
updateChildren
4、知识点四
问题解答
知道什么是 diff 算法,是 linux 的基础命令
vdom 中应用 diff 算法是为了找出需要更新的节点
vdom 实现过程,createElement 和 updateChildren
与核心函数 patch 的关系