virtual dom
什么是vdom,为何使用vdom
- virtual dom,虚拟dom
- 通过js模拟dom结构
- 目的是为了减少dom操作,dom操作是浏览器中最耗费性能的事。我们在使用操作dom的时候,比如要将ul中的3个li中的第二个li删除,我们需要删除3个li,再引入2个没删除的li,但这样太耗费性能,我们想要直接删除中间的li,不用操作另外两个,这样dom的变化就更少,性能更高,想要找出哪些dom该操作,哪些不该操作,就要运行很多逻辑,很多运算。在前端3门语言中,只有js是图灵完备语言,可以实现各种逻辑,循环,算法,js运行效率高,所以通过js语言来实现virtual dom
- 用js模拟出的dom结构需要进行对比才知道哪些需要修改,哪些不要修改。用到diff算法。
- 用来提升重绘repaint性能。
vdom如何应用,它的核心API是什么
- vdom可以通过各种开源库来实现,vue中通过snabbdom库来实现的vdom,下面以snabbdom库的使用为例子来应用
- 核心API:h('<标签名>', {...属性...}, [...子元素...]);
- h('<标签名>', {...属性...}, '文本节点');
- patch(container, vnode)
- patch(vnode, newVnode)
示例代码:
需引入snabbdom库再继续,
var data = [{ name: '张三', age: 18, address: '北京' },{ name: '李四', age: 19, address: '上海' }, { name: '王五', age: 20, address: '广州' }]; data.unshift({ name: '姓名', age: '年龄', address: '地址' }); var snabbdom = window.snabbdom; var patch = snabbdom.init([ snabbdom_class, snabbdom_props, snabbdom_style, snabbdom_eventlisteners ]); var h = snabbdom.h; 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; } render(data); var btn = document.getElementById('btn'); btn.addEventListener('click', function(e) { data[2].name = 'xiaoming'; data[3].age = 60; render(data); })
diff算法小了解,vdom为何用diff算法,以及实现的核心流程
- dom操作昂贵,减少dom操作
- 找出本次dom必须更新的节点来更新,其他的不更新
- 这个"找出"的过程就需要diff算法。
实现的核心流程分成两种情况,一种是从无到有,一种是新节点替换旧节点,核心流程(只是大概流程,没有细节)如下:
var vnode = { tag: 'ul', attrs: { id: 'idname' }, children: [{ tag: 'li', attrs: {} },{ tag: 'li', attrs: { className: 'item2' }, children: [] }] }; // patch(container, vnode); 从无到有 function createElement(vnode) { var tag = vnode.tag; var attrs = vnode.attrs || {}; var children = vnode.children || []; if (!tag) { return null; } // 创建元素 var elem = document.createElement(tag); // 属性 for (var p in attrs) { if (attrs.hasOwnProperty(p)) { // 给 elem 添加属性 elem.setAttribute(p, attrs[p]); } } // 子元素 children.forEach(function(childVnode) { // if (!childVnode) { // return elem; // } // 递归调用 createElement 创建子元素 elem.appendChild(createElement(childVnode)); }) return elem; } document.body.appendChild(createElement(vnode)); // patch(vnode, newVnode) 新节点替换旧节点 function updateChildren(vnode, newVnode) { var children = vnode.children || []; var newChildren = newVnode.children || []; children.forEach(function(child, index) { var newChild = newChildren[index]; if (newChild == null) { return; } if (newChild.tag === child.tag) { updateChildren(child, newChild); } else { replaceNode(child, newChild); } }) }