Virtual DOM 系列二:核心API
为了更好的研究Virtual DOM,我选择了snabbdom来学习。相比Vue来说,snabbdom对于研究虚拟DOM更好,因为它里面没有其他干扰的东西,而且源码也比较少,因此研究起来更方便。
1. 初次体验虚拟DOM的魅力
首先我们先用snabbdom重写之前的例子:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <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.4.0/jquery.min.js"></script> <!-- 引入snabbdom相关库 --> <script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom.js"></script> <script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-class.js"></script> <script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-props.js"></script> <script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-style.js"></script> <script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-eventlisteners.js"></script> <script src="https://cdn.bootcss.com/snabbdom/0.7.3/h.js"></script> <script type="text/javascript"> var snabbdom = window.snabbdom; //init patch function var patch = snabbdom.init([ snabbdom_class, snabbdom_props, snabbdom_style, snabbdom_eventlisteners ]); // 定义h函数 var h = snabbdom.h; var container = document.getElementById('container'); var data = [{ name: "张三", age: 24, address: "深圳" }, { name: "张三", age: 24, address: "深圳" }, { name: "张三", age: 24, address: "深圳" }]; var oldVnode; function render(data) { var vnode = h('ul#list', {}, data.map(function (item) { return h('li', {}, item.name+' '+item.age+' '+item.address) })) if (!oldVnode) { //初始化页面渲染 patch(container, vnode); } else { //对比原来的vnode和新生成的vnewnode,找出差异,只渲染修改的部分 patch(oldVnode, vnode); } oldVnode = vnode; } $('#btn-change').click(function () { data[1].name = '李四'; data[2].name = '王五'; //修改后重新渲染 render(data); }) render(data); //初始化页面渲染 </script> </body> </html>
点击change,发现只修改了有差异的地方。对比之前jquery清空整个div,性能上有很大提升,特别是在复杂应用上。
2. 实现原理
通过上面snabbdom的例子,我们发现有两个核心的API:
1. h函数(将真实dom映射成虚拟节点);
h('<html 标签名>',{属性},[children])//含有子节点的
h('<html 标签名>',{属性},'text'])//没有子节点,只有文本,如<p>this is VN</p>
2. patch函数(通过对比新旧虚拟节点,找出差异(diff算法),再把这些变化更新到真实dom中)
patch(container, vnode)//初次渲染
patch(oldVnode, newVnode); //re-render