Virtual Dom 是虚拟DOM,咱们用JS来模拟DOM结构,结构相似下面的代码:javascript
{
tag:'ul',
attrs:{
id:'list'
}
children:[
{
tag:'li',
attrs:{className:'item'},
children:['item 1']
},
{ tag:'li',
attrs:{className:'item'},
children:['item 2']
}
]
}复制代码
以上代码模拟的就是这样的DOM结构css
<ul>
<li class='item'>item 1</li>
<li class='item'>item 2</li></ul>复制代码
那么为何会有VDOM(virtual dom简称)这样的结构呢?html
咱们来模拟这样的一个场景需求。vue
1.有一堆数据,须要将数据渲染成表格java
2.随便修改一个信息,表格也会跟着变化node
若是没有VDOM,咱们会用这样的代码来完成需求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> 复制代码
上面的代码虽然完成了需求,可是,遗憾的是,若是我只是修改一部分数据,整个table算法
都须要所有渲染。对于浏览器而言,渲染DOM是一个很是“昂贵“的过程。那么,有没有什么办法,修改部分数据的时候,只是渲染我修改的DOM呢?浏览器
咱们首先来使用一下snabbdom这个库,它会利用VDOM来实现局部渲染。一块儿来感觉一下bash
<!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) { // 若是已经渲染了 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>复制代码
以上代码,则实现了当你修改了部分数据时,只渲染一部分数据。
那么,以上代码的核心就是两个函数,咱们须要对此来作探讨。一个是函数h,一个是函数patch
函数h返回的值是一个vnode,也就是虚拟DOM节点,如图所示
也就是说使用h函数能够生成相似于右边的vnode结构。
那么关键函数patch的做用,则是将vnode渲染成真实的DOM节点,而后塞入到容器里面。
若是容器里面已经有生成好的vnode,那么,则会将新生成的newVnode和以前的vnode相比较,而后将不一样的节点找出来,而后代替旧的节点。
到如今为止,已经基本上了解了VDOM的含义以及为何会用VDOM,咱们来作一个简单的总结
那么,接下来又出现了一个问题,咱们如何知道哪一个节点须要更新呢?这就是diff算法的做用
由于diff算法自己太过于复杂,因此只须要理解一下核心的思想便可。
那么咱们只须要关注在渲染的时候,发生了什么事情,理解下面这两个事件的核心流程便可。
也就是说,咱们须要理解的是:
咱们要实现的是这样的过程:
咱们来模拟一下上面的建立过程,只是伪代码,咱们了解大体的流程
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
}复制代码
那么,经过上面的模拟代码,已经能够很好的了解最开始将vdom渲染到容器的过程。
这个过程就是将newVnode和vnode对比,将差别进行渲染的部分。
那么伪代码流程以下:
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)//生成新的真实的dom节点
// 替换
}复制代码
那么真正的替换过程有哪些呢?简单的总结一下:
elem
newVnode
和oldVnode
是否指向同一个对象,若是是,那么直接return
el
的文本节点设置为Vnode
的文本节点。oldVnode
有子节点而newVnode
没有,则删除el
的子节点oldVnode
没有子节点而newVnode
有,则将Vnode
的子节点真实化以后添加到elem
updateChildren
函数比较子节点,这一步很重要,请参考这篇文章以上只是简单的理解了diff算法的流程,关于更多的diff算法的详细过程,能够阅读参考文章。