背景

我们都知道频繁的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. 【MFC】转:在CHtmlView中判断页面加载完成

    在列出别人的代码前,记录下自己的,覆盖父类的OnNavigateComplete2函数即可. typedef struct _tagEventURL { CString strUrl; HANDLE ...

  2. 横向开关(switch)

    横向开关(switch) 一:属性 1.Activity //横向开关 public class SwitchActivity extends Activity { private Switch sw ...

  3. Python爬虫教程-08-post介绍(百度翻译)(下)

    Python爬虫教程-08-post介绍(下) 为了更多的设置请求信息,单纯的通过urlopen已经不太能满足需求,此时需要使用request.Request类 构造Request 实例 req = ...

  4. xml-treeview转换

    XML——>treeciew using System; using System.Collections.Generic; using System.Text; using System.Wi ...

  5. 5 hbase-shell + hbase的java api

    本博文的主要内容有 .HBase的单机模式(1节点)安装 .HBase的单机模式(1节点)的启动 .HBase的伪分布模式(1节点)安装  .HBase的伪分布模式(1节点)的启动    .HBase ...

  6. August 17th 2017 Week 33rd Thursday

    Fate is responsible for shuffling, but the game of cards is our own! 命运负责洗牌,但是玩牌的是我们自己! Today, I upd ...

  7. python30 excel修改模块xlutils

    xlrd只读,xlwt只写,xlutils模块则将读写功能结合起来.https://pypi.org/project/xlutils/ 修改excel通过xlutils的copy函数将<clas ...

  8. SGU---462 Electrician 最大生成树

    题目链接: https://cn.vjudge.net/problem/SGU-462 题目大意: 有N条电线需要接入电网,第i条电线计划连接ai和bi两个地点,电线有两个属性:ri(电线稳定度)和c ...

  9. My97datepicker使用方法

    My97DatePicker是一款非常灵活好用的日期控件.使用非常简单. 1.下载My97DatePicker组件包 2.在页面中引入该组件js文件:     <script type=&quo ...

  10. 20145324 Java实验三

    一.git 上传代码步骤 上传结果 原代码 下载同学代码 更改 二.重构 原代码 rename 原代码 实验总结 这次实验比较简单,而且终于解决了git的问题,很开心 步骤 耗时 百分比 需求分析 1 ...