JavaScript从初级往高级走系列————Virtual Dom
原文博客地址:https://finget.github.io/2018/05/22/virtualDom/
什么是虚拟DOM
- 用JS模拟DOM结构
- DOM变化的对比,放在JS层来做(图灵完备语言)
- 提高重绘性能
重绘和回流
页面渲染过程:
- 当render tree中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建。这就称为回流(reflow)。
- 当render tree中的一些元素需要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局的,比如background-color。则就叫称为重绘。
模拟虚拟DOM
<ul id="list"><li class="item">Item 1</li><li class="item">Item 2</li>
</ul>
// js模拟虚拟DOM
{tag: 'ul',attrs:{id: 'list'},children:[{tag: 'li',attrs: {className: 'item'},children: ['Item 1']},{tag: 'li',attrs: {className: 'item'},children: ['Item 2']}]
}
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Document</title><script src="https://cdn.bootcss.com/jquery/2.2.0/jquery.min.js"></script>
</head>
<body><div id="container"></div><button id="btn-change">change</button><script>var data = [{name: '张三',age: '20',address: '北京'},{name: '王五',age: '22',address: '成都'},{name: '李四',age: '21',address: '上海'}]// 渲染函数function render(data) {var $container = $('#container');// 清空容器,重要!!!$container.html('');// 拼接 tablevar $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操作是‘昂贵’的,js运行效率高
- 尽量减少DOM操作,尽量减少回流重绘
虚拟DOM如何应用,核心API是什么
介绍 snabbdom
snabbdom GitHub地址
官网例子:
var snabbdom = require('snabbdom');
var patch = snabbdom.init([ // Init patch function with chosen modulesrequire('snabbdom/modules/class').default, // makes it easy to toggle classesrequire('snabbdom/modules/props').default, // for setting properties on DOM elementsrequire('snabbdom/modules/style').default, // handles styling on elements with support for animationsrequire('snabbdom/modules/eventlisteners').default, // attaches event listeners
]);
var h = require('snabbdom/h').default; // helper function for creating vnodesvar container = document.getElementById('container');// h函数生成一个虚拟节点
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); // 把vnode加入到container中// 数据改变,重新生成一个newVnode
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
// 将newVnode更新到之前的vnode中,从而更新视图
patch(vnode, newVnode); // Snabbdom efficiently updates the old view to the new state
snabbdom h 函数
var vnode = h('ul#list',{},[h('li.item',{},'Item 1'),h('li.item',{},'Item 2')
]){tag: 'ul',attrs:{id: 'list'},children:[{tag: 'li',attrs: {className: 'item'},children: ['Item 1']},{tag: 'li',attrs: {className: 'item'},children: ['Item 2']}]
}
snabbdom patch 函数
var vnode = h('ul#list',{},[h('li.item',{},'Item 1'),h('li.item',{},'Item 2')
])
var container = document.getElementById('container');
patch(container, vnode);// 模拟改变
var btnChange = document.getElementById('btn-change');
btnChange.addEventListener('click',function(){var newVnode = h('ul#list',{},[h('li.item',{},'Item 111'),h('li.item',{},'Item 222'),h('li.item',{},'Item 333')])patch(vnode, newVnode);
})
snabbdom例子
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Document</title><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.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/h.js"></script><script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-eventlisteners.js"></script>
</head>
<body><div id="container"></div><button id="btn-change">change</button><script>var snabbdom = window.snabbdom;// 定义 patchvar patch = snabbdom.init([snabbdom_class,snabbdom_props,snabbdom_style,snabbdom_eventlisteners])// 定义 hvar h = snabbdom.h;var container = document.getElementById('container');// 生成 vnodevar vnode = h('ul#list',{},[h('li.item',{},'Item 1'),h('li.item',{},'Item 2')])patch(container, vnode);// 模拟数据改变var btnChange = document.getElementById('btn-change');btnChange.addEventListener('click',function(){var newVnode = h('ul#list',{},[h('li.item',{},'Item 1'),h('li.item',{},'Item 222'),h('li.item',{},'Item 333')])patch(vnode, newVnode);})</script>
</body>
</html>
看图,只有修改了的数据才进行了刷新,减少了DOM操作,这其实就是vnode与newVnode对比,找出改变了的地方,然后只重新渲染改变的
重做之前的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;// 定义关键函数 patchvar patch = snabbdom.init([snabbdom_class,snabbdom_props,snabbdom_style,snabbdom_eventlisteners]);// 定义关键函数 hvar h = snabbdom.h;// 原始数据var data = [{name: '张三',age: '20',address: '北京'},{name: '王五',age: '22',address: '成都'},{name: '李四',age: '21',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-renderpatch(vnode, newVnode);} else {// 初次渲染patch(container, newVnode);}// 存储当前的 vnode 结果vnode = newVnode;}// 初次渲染render(data)var btnChange = document.getElementById('btn-change')btnChange.addEventListener('click', function () {data[1].age = 30data[2].address = '深圳'// re-renderrender(data)})</script>
</body>
</html>
核心API
- h('<标签名>',{...属性...},[...子元素...])
- h('<标签名>',{...属性...},'...')
- patch(container,vnode)
- patch(vnode,newVnode)
简单介绍 diff 算法
什么是 diff 算法
这里有两个文本文件:
借用git bash
中 diff
命令可以比较两个文件的区别:
在线diff工具
虚拟DOM ---> DOM
// 一个实现流程,实际情况还很复杂
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 attrNamefor (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
}
vnode ---> newVnode
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);// 替换
}
最后
创建了一个前端学习交流群,感兴趣的朋友,一起来嗨呀!
JavaScript从初级往高级走系列————Virtual Dom相关推荐
- JavaScript从初级往高级走系列————prototype
原文博客地址:https://finget.github.io/2018/05/10/javascript-prototype/ 原型 下面内容为转载的,原地址,写的真的很好! 构造函数创建对象 我们 ...
- 深入框架本源系列 —— Virtual Dom
该系列会逐步更新,完整的讲解目前主流框架中底层相通的技术,接下来的代码内容都会更新在 这里 为什么需要 Virtual Dom 众所周知,操作 DOM 是很耗费性能的一件事情,既然如此,我们可以考虑通 ...
- 6本适合初级到高级HTML5程序员阅读的书籍推荐,读完事半功倍
随着移动互联网的日益兴起,IT行业对于前端的需求也在不断的提高,那么从前端小白修炼成为HTML5前端大神的这个过程之中,一些必备的枕边书也是必不可少的. 俗话说书籍是人类进步的阶梯,对HTML5程序员 ...
- 原生 遍历_细品原生JS从初级到高级知识点汇总(三)
作者:火狼1 转发链接:https://juejin.im/post/5daeefc8e51d4524f007fb15 目录 细品原生JS从初级到高级知识点汇总(一) 细品原生JS从初级到高级知识点汇 ...
- 年底了,如何准备 Java 初级和高级的技术面试?
作者:hsm_computer 来自:cnblogs.com/JavaArchitect/p/9032323.html 本人最近几年一直在做java后端方面的技术面试官,而在最近两周,又密集了面试了一 ...
- 初级中级高级_初级职位,(半)高级职位
初级中级高级 As a recent hire at my new job, as expected, a lot of things seemed scary and overwhelming. T ...
- 如何准备Java初级和高级技术的面试呢?
IT行业的崛起带动了一大批的新兴职业,Java数据开发就是其中之一,作为IT行业的刚需职位,企业对合格的Java开发人员求贤若渴, 在各大主流招聘平台上, Java相关职位数量一直名列前茅,那么我们如 ...
- java面试总结(一)-----如何准备Java初级和高级的技术面试
java面试总结(一)--如何准备Java初级和高级的技术面试 本文内容来自:https://mp.weixin.qq.com/s?__biz=MzAxNDMwMTMwMw==&mid=224 ...
- IT:后端进阶技术路线图(初级→中级→高级)、后端开发工程师(技术方向分类之后台业务开发/中间件/内核/分布式架构)基础知识简介、技术路线/技术趋势指南(如何选择自己的技术方向)之详细攻略
IT:后端进阶技术路线图(初级→中级→高级).后端开发工程师(技术方向分类之后台业务开发/中间件/内核/分布式架构)基础知识简介.技术路线/技术趋势指南(如何选择自己的技术方向)之详细攻略 目录 后端 ...
最新文章
- php面向对象实践,【技术产品】PHP中的面向对象实践-基本实践案例
- 财务大数据比赛有python吗-如何在一个月之内掌握python数据分析,参加大数据比赛?...
- 【无标题】RestHighLevelClient工具类
- Python脚本实现图片加水印
- Android笔记 Android客户端从服务器获取源码乱码demo
- 将计算机知识应用于生活中,电脑知识在生活中的灵活运用(6页)-原创力文档...
- JSP-session编写购物车
- 游戏服务器开发技术栈
- 如何制作Excel表头
- Python语言程序设计 测验6: 组合数据类型 (第3周)
- 7-1 愿天下有情人都是失散多年的兄妹 (25 分)
- etr2模式,时力高HXD1C转换开关KRGV+ETR2
- 白话 P-value 这个再通俗不过了~
- opencv ipcam
- nodejs (usb)连接打印机 获取打印状态(escpos-printer和node-escpos)检测USB端口的热插拔
- linux怎样收集系统信息,Linux下收集系统和硬件信息的10个实用命令
- 【蓝牙系列】蓝牙5.4到底更新了什么(2)
- 【电力系统】经济调度、最优潮流、机组组合
- SQL查记录总数-总数统计的方法
- 商城App接入快递100