1.vdom是什么?为何存在vdom?
virtual dom,虚拟dom,用js模拟DOM结构。
DOM变化对比放在JS层来做,提高重绘性能。
1.2 代码演示
需求:根据数据生成表格,点击按钮,数据改变,表格内容也跟着改变。
1.2.1 jquery实现
var data = [
{
name: '张三',
age: 20,
address: '北京'
},
{
name: '李四',
age: 19,
address: '深圳'
},
{
name: '王五',
age: 22,
address: '上海'
}
]
// 渲染函数
function render (data) {
var $container = $('#container')
$container.html('')
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').click(function () {
data[0].name = '小红'
data[2].age = 33
// re-render
render(data)
})
// 首次渲染
render(data)
遇到的问题:
dom操作是昂贵的,js运行效率高。
尽量减少dom操作。
项目越复杂,影响就越严重。
2.vdom如何应用,核心API是什么?
通过介绍snabbdom来学习vdom。
2.1 核心API
h函数和patch函数。
h('标签名', {属性}, ['子元素'])
h('标签名', {属性}, '子元素')
patch(container, vnode)
patch(node, newVnode)
h函数生成vnode,patch函数用来首次生成dom,以及比对两个vnode,找出差异,针对差异生成新的dom
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', {}, 'Item1'),
h('li.item', {}, 'Item2')
])
patch(container, vnode)
document.getElementById('btn').addEventListener('click', function () {
var newVnode = h('ul#list', {}, [
h('li.item', {}, 'Item1'),
h('li.item', {}, 'ItemB'),
h('li.item', {}, 'Item3'),
])
patch(vnode, newVnode)
})
2.2 重做1.2.1中的代码
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: 19,
address: '深圳'
},
{
name: '王五',
age: 22,
address: '上海'
}
]
data.unshift({
name: 'name',
age: 'age',
address: '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) {
patch(vnode, newVnode)
} else {
patch(container, newVnode)
}
vnode = newVnode
}
document.getElementById('btn').addEventListener('click', function () {
data[3].name = '小红'
data[2].age = 33
// re-render
render(data)
})
render(data)
3.介绍一下diff算法
3.1 为什么使用diff算法
DOM操作是昂贵的,因此要减少dom操作,找出本次DOM必须更新的节点来更新,其他的不更新。
这个找出的过程,就需要diff算法。
3.2 实现过程
patch(container, vnode)
patch(node, newVnode)
我们只需要简单了解即可,所以从这两个方法入手。
// vdom结构
{
"tag": "div",
"attr": {
"id": "list"
},
children: [
{
"tag": "div",
"attr": {
"className": "item"
},
"children": ["item1"]
}
]
}
// patch(dom, node) 大概实现逻辑
function createElement(vnode) {
var tag = vnode.tag
var attrs = vnode.attrs || {}
var children = vnode.children || []
if (!tag) {
return null
}
// 创建
var elem = document.createElement(tag)
// 属性
var attrName
for (attrName in attrs) {
if (attrs.hasOwnProperty(attrName)) {
elem.setAttribute(attrName, attrs[attrName])
}
}
// 子元素
children.forEach(function (childVnode) {
// 给elem添加子元素
elem.appendChild(createElement(childVnode))
})
return elem
}
// patch(vnode, newVnode) 大概实现逻辑
function updateChildren (vnode, newVnode) {
var children = vnode.children || []
var newChildren = vnode.children || []
children.forEach(fucntion (childVnode, index) {
var newChildVnode = newChildren[index]
if (childVnode.tag === childVnode.tag) {
// 递归深层次对比
updateChildren(childVnode, childVnode)
} else {
// 替换
replaceNode(childVnode, newChildVnode)
}
})
}
function replaceNode () {
// 省略dom替换操作
}
其他内容不深入研究,只做到现在了解即可。