浅谈 Virtual DOM 的那些事
背景
我们都知道频繁的dom给我们带来的代价是昂贵的,例如我们有时候需要去更新Table 的部分数据,必须去重新重绘表格,这代价实在是太大了,相比于频繁的手动去操作dom而带来性能问题,vdom很好的将dom做了一层映射关系,进而将在我们本需要直接进行dom的一系列操作,映射到了操作vdom.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>virtualDom</title>
</head>
<body>
<div id="container"></div>
<button id="btn-change">修改</button>
<script type="application/javascript" src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
<script type="application/javascript">
const dataSource = [{
key: '1',
name: '胡彦斌',
age: 32,
address: '西湖区湖底公园1号'
}, {
key: '2',
name: '胡彦祖',
age: 42,
address: '西湖区湖底公园1号'
}]; const columns = [{
title: '姓名',
dataIndex: 'name',
key: 'name',
}, {
title: '年龄',
dataIndex: 'age',
key: 'age',
}, {
title: '住址',
dataIndex: 'address',
key: 'address',
}];
function render(data) {
var container = $('#container');
container.html(''); //清空容器
//添加表头
var $table =$('<table>')
$table.append($('<tr>'))
columns.map(function(item,index){
$table.append($('<td>'+item.title+'</td>'))
})
$table.append($('</tr>'))
//添加表体
dataSource.forEach(function(item){
$table.append($('<tr></tr><td>'+item.name+'</td>'+'<td>'+item.age+'</td>'+'<td>'+item.address+'</td></tr>'))
})
//只渲染一遍dom,尽然如此,还是需要清空容器
container.append($table)
}
$('#btn-change').click(function(){
dataSource[0].name="胡军网";
dataSource[1].address='南山区沙河东路1号'
//re——render
render(dataSource)
})
render()
</script>
</body>
</html>
解决
- virtual dom,虚拟 DOM
- 用 JS 模拟 DOM
什么是vdom
HTML DOM 结构:
<ul id="ul-list">
<li class="item">Item 1</li>
<li class="item">Item 2</li>
<li class="item">Item 3</li>
</ul>
针对于上面HTML DOM 结构,可以用JS表示为:
var ulE = {
tagName: 'ul', // 标签名
props: { // 属性用对象存储键值对
id: 'ul-list'
},
children: [ // 子节点
{tagName: 'li', props: {className: 'item'}, children: ["Item 1"]},
{tagName: 'li', props: {className: 'item'}, children: ["Item 2"]},
{tagName: 'li', props: {className: 'item'}, children: ["Item 3"]},
]
}
JS对象中抽取公共的部分属性,进一步封装:
export default Ele = (tagName, props, children) => {
this.tagName = tagName
this.props = props
this.children = children
}
import * as el from 'ele';
var ol = el('ul', {id: 'ul-list'}, [
el('li', {className: 'item'}, ['Item 1']),
el('li', {className: 'item'}, ['Item 2']),
el('li', {className: 'item'}, ['Item 3'])
]);
通过snabbdom进行virtual dom(核心API:h函数、patch函数)
案例一: 对比局部更新添加修改ul中的li
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>snabbdom</title>
</head>
<body>
<div id="container"></div>
<button id="btn-change">修改</button>
<script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom.min.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-class.min.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-props.min.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-style.min.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-eventlisteners.min.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.1/h.min.js"></script>
<script type="application/javascript">
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');
//定义 virtual node
var vnode = h('ul#ul-list',{},[
h('li.item',{},'item1'),
h('li.item',{},'item2')
])
patch(container,vnode);
document.getElementById('btn-change').addEventListener('click',function () {
var newVnode = h('ul#ul-list',{},[
h('li.item',{},'item1'),
h('li.item',{},'西湖区湖底公园1号'),
h('li.item',{},'西湖区湖底公园2号'),
h('li.item',{},'西湖区湖底公园3号')
])
patch(vnode,newVnode);
})
</script>
</body>
</html>
item1 所在的li不会进行dom渲染,只有新增或者修改的node才会发生改变,执行结果如下所示:
案例二: 局部更新部分Table 数据(使用Vitual DOM 性能的提升)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>snabbdom</title>
</head>
<body>
<div id="container"></div>
<button id="btn-change">修改</button>
<script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom.min.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-class.min.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-props.min.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-style.min.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-eventlisteners.min.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.1/h.min.js"></script>
<script type="application/javascript">
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');
const dataSource = [{
key: '1',
name: '胡彦斌',
age: 32,
address: '西湖区湖底公园1号'
}, {
key: '2',
name: '胡彦祖',
age: 42,
address: '西湖区湖底公园1号'
}]; const columns = [{
title: '姓名',
dataIndex: 'name',
key: 'name',
}, {
title: '年龄',
dataIndex: 'age',
key: 'age',
}, {
title: '住址',
dataIndex: 'address',
key: 'address',
}];
var vdom=null;
function render(dataSource) {
var titleTr= [];
titleTr.push(h('td',{},' '))
columns.forEach(function(item){
if(item.hasOwnProperty('title')){
titleTr.push(h('td',{},item['title']))
}
})
var vTitle = h('tr',{},titleTr);
var vBody =dataSource.map(function(item){
const vp= []
for(var i in item) {
if(item.hasOwnProperty(i)){
vp.push(h('td',{},item[i]))
}
}
return h('tr',{},vp)
})
vBody.unshift(vTitle);
var vTable = Object.assign([],vBody);
var newVnode = h('table',{},vTable)
if(!vdom){
vdom = newVnode;
patch(container,vdom);
}else{
patch(vdom,newVnode);
}
}
document.getElementById('btn-change').addEventListener('click',function () {
dataSource[0].name="胡军网";
dataSource[1].address='南山区沙河东路1号'
//re——render
render(dataSource)
})
render(dataSource)
</script>
</body>
</html>
执行结果如下所示:
patch函数——patch(container,vDom)过程的简单实现
/**
*
* @param container 容器
* @param vDom 虚拟dom
* @constructor
*/ var ulE = {
tagName: 'ul', // 标签名
props: { // 属性用对象存储键值对
id: 'ul-list'
},
children: [ // 子节点
{tagName: 'li', props: {className: 'item'}, children: ["Item 1"]},
{tagName: 'li', props: {className: 'item'}, children: ["Item 2"]},
{tagName: 'li', props: {className: 'item'}, children: ["Item 3"]},
]
} export default function VDomCreateElement(vDom){
var tagName=vDom.tagName || '';
var props =vDom.props || {};
var children =vDom.children || [];
var tagNameEle =document.createElement(tagName);
for(var prop in props){
if(props.hasOwnProperty(prop)){
tagNameEle.setAttribute(prop,props[prop])
}
}
if(!children){
return tagNameEle;
}else{
children.forEach(function(item){
tagNameEle.appendChild(VDomCreateElement(item)) //不断递归生成child Node
})
}
return tagNameEle;
}
patch函数——patch(vDom,newVDom)过程的简单模拟实现
/**
* vDOM 简单diff 对比 新的dom渲染到旧的dom
* @param vDom 老vDom
* @param newVDom 新vDom
*/
export function vDomDiff(vDom,newVDom){
var vDomChilden = vDom.children || [];
var newVDomChilden = newVDom.children || [];
//假设 tagName 相同
vDomChilden.forEach(function(item,index){
if(!newVDomChilden[index]){
return;
}
if(item.tagName === newVDomChilden[index].tagName){
//两者tagName 一样 递归
VDomCreateElement(item,newVDomChilden[index]);
}else {
//两者tagName 不一样 替换
replaceNode(item,newVDomChilden[index])
}
})
} /**
* dom操作 替换
* @param vDom
* @param newVDom
*/
function replaceNode(vDom,newVDom){
//dom操作 node替换
// .... }
Visual DOM 为何使用diff算法
Visual DOM找出DOM 中不同,进而更新DOM,diff算法同样也是找出文件中的不同进行对比,diff应用在linux,git……,
源码地址
https://github.com/10086XIAOZHANG/VirtualDOMDemo
浅谈 Virtual DOM 的那些事的更多相关文章
- 浅谈Virtual Machine Manager(SCVMM 2012) cluster 过载状态检测算法
在我们使用scvmm2012的时候,经常会看到群集状态变成了这样 点开看属性后,我们发现是这样 . 发现了吗?Over-committed,如果翻译过来就是资源过载,或者说资源过量使用了,那么这个状态 ...
- 浅谈JavaScript DOM编程艺术读后感和一点总结
最近工作不是很忙就想想想JavaScript的内部组成和一些要点,就是从这本书开始的.对新手来说还好,简单易懂. 简单终结下我重书中学到的一些要点. 下面都是个人学习的要点提取: 1.给自己预留退路, ...
- 浅谈Blazor开发的那些事
在这篇文章中,我们将解决一些常见的Blazor问题.具体来说就是"什么是Blazor",但更重要的是"为什么要用Blazor".既然我们已经有了Angular. ...
- 浅谈 kubernetes service 那些事 (下篇)
欢迎访问网易云社区,了解更多网易技术产品运营经验. 五.K8s 1.8 新特性--ipvs ipvs与iptables的性能差异 随着服务的数量增长,IPTables 规则则会成倍增长,这样带来的问题 ...
- 浅谈DOM性能考虑
浅谈DOM性能考虑 很多人都会忽视脚本对Web应用整体性能的影响.为保证应用的流畅运行,在为文档编写和应用脚本时,需要注意一些问题.一.尽量减少访问DOM和尽量减少标记 访问DOM的方式对脚本性 ...
- Js之浅谈dom操作
JavaScript之浅谈dom操作 1.理解dom: DOM(Document Object Model ,文档对象模型)一种独立于语言,用于操作xml,html文档的应用编程接口. 怎么说,我从两 ...
- 浅谈 kubernetes service 那些事(上篇)
一.问题 首先,我们思考这样一个问题: 访问k8s集群中的pod, 客户端需要知道pod地址,需要感知pod的状态.那如何获取各个pod的地址?若某一node上的pod故障,客户端如何感知? 二.k8 ...
- 前端性能优化--为什么DOM操作慢? 浅谈DOM的操作以及性能优化问题-重绘重排 为什么要减少DOM操作 为什么要减少操作DOM
前端性能优化--为什么DOM操作慢? 作为一个前端,不能不考虑性能问题.对于大多数前端来说,性能优化的方法可能包括以下这些: 减少HTTP请求(合并css.js,雪碧图/base64图片) 压缩( ...
- 浅谈 Qt 布局那些事
Qt 布局那些事是本文介绍的内容,直接进入主题.GridLayout是一个非常强大的布局管理器,它可以实现很多复杂的布局,名字中暗示它将所有控件放置在类似网格的布局中.^__^GridLayout有两 ...
随机推荐
- SpringBoot整合Slf4j+logback日志框架
一.Slf4j简单介绍与优势 1.介绍 Slf4j的全称是Simple Loging Facade For Java(Java简单日志门面),它仅仅是一个为Java程序提供日志输出的统一接口,并不是一 ...
- 在主线程中慎用WaitForSingleObject (WaitForMultipleObjects)
下面的代码我调试了将近一个星期,你能够看出什么地方出了问题吗?线程函数: DWORD WINAPI ThreadProc( while(!bTerminate) { // 从 ...
- 在Oracle/SQL Service中通过Function返回Table
本函数用途:返回一个Table 在Oracle中实现,范例: --在Types中: create or replace type objTable as object ( s_usercode var ...
- Python爬虫教程-11-proxy代理IP,隐藏地址(猫眼电影)
Python爬虫教程-11-proxy代理IP,隐藏地址(猫眼电影) ProxyHandler处理(代理服务器),使用代理IP,是爬虫的常用手段,通常使用UserAgent 伪装浏览器爬取仍然可能被网 ...
- elasticsearch 使用tcp 访问NoNodeAvailableException[None of the configured nodes are available: [{#transport#-1}{Yk0WjtKbQXqYCJSDFRYlRA}
默认的 elasticsearch.yml 端口是9200,是给tcp提供的.如果想使用 自带的 TransportClient 需要配置为 tcp 的9300端口.配置方式为: 在/config/ ...
- java多线程处理问题
今天碰到个以前的线上bug需要处理下:问题是这样的,我们的app里面有个点赞的功能,点赞完后显示点赞人列表以及点赞数量,但是数量现在总是不准确.之后查看代码,发现点赞时候只是简单的向数据库添加了一条点 ...
- 常用的自动化测试框架及测试框架的发展(Alpha)
前言:自动化测试在过去的20年已经有了很大的发展.最初的测试工具只提供了简单的捕捉/回访功能,维护性较差.而且脚本工具实现需要很强的开发技术和经验,而且数量众多的测试脚本加上没有文档记录因此维护起来较 ...
- MVC实例分析1.1
E_S源码百度云分享链接: http://pan.baidu.com/s/1dFHzEJv 思维导图源文件分享链接: http://pan.baidu.com/s/1hrAXGC8 简单PPT分享链接 ...
- Windows环境下使用Nginx搭建负载均衡
前言 最近没有什么事情,喜欢总结并学习东西!前几天写来一个Session共享,那么我们为什么需要Session共享?因为我们的应用程序分布在多个服务器上,为了合理分配用户的请求,就需要用到负载均衡技术 ...
- 阿里云Quick BI——让人人都成为分析师
在3月29日深圳云栖大会的数据分析与可视化专场中,阿里云产品专家潘炎峰(陌停)对大数据智能分析产品 Quick BI 进行了深入的剖析.大会现场的精彩分享也赢得观众们的一直认可和热烈的反响. Quic ...