背景

我们都知道频繁的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. System.Media.Color与System.Drawing.Color转换方法

    public static System.Media.Color GetMediaColorFromDrawingColor(System.Drawing.Color color) {      re ...

  2. 专访探探DBA张文升:PG在互联网应用中同样也跑的很欢畅

    张文升认为,PG无论在可靠性和性能方面都不输其它任何关系型数据库   张文升,探探DBA,负责探探的数据库架构.运维和调优的工作.拥有8年开发经验,曾任去哪儿网DBA.   9月24日,张文升将参加在 ...

  3. java中字节流与字符流以及字节流多余字节问题

    1.字节流 字节流byte为单位对文件的数据进行写入与读取操作.字节的方式在复制音频图片文件时比较适用,但在对于普通文件的读写上有两大缺陷: 第一,字节流在读取中文字符时,若设定的字节数组长度刚好末尾 ...

  4. win8.1 安装msi软件出现 2503、2502

    问题现象: 安装Msi封包的程序的时候,老是提示 2503 和 2502 错误. 解决办法: 命令提示符提示安装程序权限 右击开始按钮,然后选择命令提示如(管理员)

  5. 打通版微社区(6):部署微信插件及开通QQ云服务

    写在前面: 此文是我最后写的.其实实际部署的时候,我是先安装了论坛并试图开通微信的微社区.发现微社区需要在微信公众平台的开发者中心里配置 "网页账号,网页授权获取用户基本信息"为论 ...

  6. 记录windows下编译chromium,备忘

    编译windows下chromium,时间:20170619, 官方地址:https://chromium.googlesource.com/chromium/src/+/master/docs/wi ...

  7. 转贴:如何学好C++语言.docx

    不知道哪里转的.呵呵 抱歉 C++是最难的语言.这个世界上最难的编程语言可能非C++莫属了.你千万不要以为几天就可以学好C++,C++的学习曲线是相当BT的,你可以看看这篇文章.C++是一门很自由的语 ...

  8. 今天开始每天一点ffmpeg知识。千里之行 。

    今天开始每天一点ffmpeg知识.千里之行 .

  9. 获取应用程序 或Web页面目录的路径

    一.Winform获取本程序的路径 1.获取当前目录 返回最后不带“\”的目录:如D:\Winform\bin\Debug System.Windows.Forms.Application.Start ...

  10. #Alpha Scrum4

    Alpha Scrum3 牛肉面不要牛肉不要面 Alpha项目冲刺(团队作业5) 各个成员在 Alpha 阶段认领的任务 林志松:音乐网页前端页面编写,博客发布 林书浩.陈远军:界面设计.美化 吴沂章 ...