背景

我们都知道频繁的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>

解决

  1. virtual dom,虚拟 DOM
  2. 用 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 的那些事的更多相关文章

  1. 浅谈Virtual Machine Manager(SCVMM 2012) cluster 过载状态检测算法

    在我们使用scvmm2012的时候,经常会看到群集状态变成了这样 点开看属性后,我们发现是这样 . 发现了吗?Over-committed,如果翻译过来就是资源过载,或者说资源过量使用了,那么这个状态 ...

  2. 浅谈JavaScript DOM编程艺术读后感和一点总结

    最近工作不是很忙就想想想JavaScript的内部组成和一些要点,就是从这本书开始的.对新手来说还好,简单易懂. 简单终结下我重书中学到的一些要点. 下面都是个人学习的要点提取: 1.给自己预留退路, ...

  3. 浅谈Blazor开发的那些事

    在这篇文章中,我们将解决一些常见的Blazor问题.具体来说就是"什么是Blazor",但更重要的是"为什么要用Blazor".既然我们已经有了Angular. ...

  4. 浅谈 kubernetes service 那些事 (下篇)

    欢迎访问网易云社区,了解更多网易技术产品运营经验. 五.K8s 1.8 新特性--ipvs ipvs与iptables的性能差异 随着服务的数量增长,IPTables 规则则会成倍增长,这样带来的问题 ...

  5. 浅谈DOM性能考虑

    浅谈DOM性能考虑 很多人都会忽视脚本对Web应用整体性能的影响.为保证应用的流畅运行,在为文档编写和应用脚本时,需要注意一些问题.一.尽量减少访问DOM和尽量减少标记    访问DOM的方式对脚本性 ...

  6. Js之浅谈dom操作

    JavaScript之浅谈dom操作 1.理解dom: DOM(Document Object Model ,文档对象模型)一种独立于语言,用于操作xml,html文档的应用编程接口. 怎么说,我从两 ...

  7. 浅谈 kubernetes service 那些事(上篇)

    一.问题 首先,我们思考这样一个问题: 访问k8s集群中的pod, 客户端需要知道pod地址,需要感知pod的状态.那如何获取各个pod的地址?若某一node上的pod故障,客户端如何感知? 二.k8 ...

  8. 前端性能优化--为什么DOM操作慢? 浅谈DOM的操作以及性能优化问题-重绘重排 为什么要减少DOM操作 为什么要减少操作DOM

    前端性能优化--为什么DOM操作慢?   作为一个前端,不能不考虑性能问题.对于大多数前端来说,性能优化的方法可能包括以下这些: 减少HTTP请求(合并css.js,雪碧图/base64图片) 压缩( ...

  9. 浅谈 Qt 布局那些事

    Qt 布局那些事是本文介绍的内容,直接进入主题.GridLayout是一个非常强大的布局管理器,它可以实现很多复杂的布局,名字中暗示它将所有控件放置在类似网格的布局中.^__^GridLayout有两 ...

随机推荐

  1. ArcGIS10.x Engine直连提示连接超时ORA-12170 来自:http://www.iarcgis.com/?p=1004

    导语 随着Esri大力宣传直连,用户也越来越由服务连接,改为直连,当然ArcGIS Engine开发用户也不例外. 环境 Oracle数据库,ArcGIS版本不限,不过由于9版本多以服务连接,以10版 ...

  2. Monte Carlo与TD算法

    RL 博客:http://blog.sciencenet.cn/home.php?mod=space&uid=3189881&do=blog&view=me&from= ...

  3. Week2——XML

    一.什么是XML? XML是可扩展标记性语言,类似于HTML,被设计为传输和存储数据,其焦点是数据的内容.XML的标签没有被预定义,用户就需要自行定义标签,也可随意定义标签.XML 允许创作者定义自己 ...

  4. flask介绍

    安装flask pip3 install flask 短小精悍.可扩展强 的一个Web框架. 牛逼点:上下文管理机制 依赖wsgi: werkzeug(相当于Django的wsgi):只要安装flas ...

  5. weixin设置菜单

    https://jingyan.baidu.com/article/925f8cb8dfcf11c0dce05661.html

  6. DW数据库整理设置

    操作管理数据库的数据整理设置如下: 管理-全局数据库设置-数据库整理(database grooming)中,选择一个数据类型进行编辑就可以了.默认是保留7天的数据.具体可以参考如下: http:// ...

  7. console 程序随系统启动及隐藏当前程序窗口

    应业务需求,程序需要与系统启动而自动运行,故加入以下代码,保存成 .bat文件 ,双击执行即可 reg add "HKEY_CURRENT_USER\Software\Microsoft\W ...

  8. xgcom linux下的串口助手

    好用到爆@@! 2.Install: Source code: http://code.google.com/p/xgcom/ svn checkout http://xgcom.googlecode ...

  9. SQL脚本修改表结构

    SQL脚本修改表结构 新建表:create table [表名]([自动编号字段] int IDENTITY (1,1) PRIMARY KEY ,[字段1] nVarChar(50) default ...

  10. VM安装CentOs7虚拟机后无法上网之解决方法

    最近在研究DC/OS的安装,读了很多安装方法后决定先从docker的安装入手,由于DC/OS的安装必须在CentOs7版本以上,所以就在VM下安装了CentOs7,殊不知安装后并不能上网,于是乎又转到 ...