当前位置:网站首页>手写VirtualDOM
手写VirtualDOM
2022-06-29 08:24:00 【CamilleZJ】
目前最流行的两大前端框架,React和Vue,都不约而同的借助Virtual DOM技术提高页面的渲染效率。那么,什么是Virtual DOM?它是通过什么方式去提升页面渲染效率的呢?
一、Virtual DOM是什么?
Virtual DOM,虚拟DOM,只是一个简单的JS对象,即用js对象去模拟dom结构。
为什么要模拟DOM结构?
如下:dom节点继承层次复杂,特别是ie中,自身的属性以及继承来的属性特别多,如下div上就有几百个属性

- 一个div上就几百个属性,其次dom继承层级比较复杂,特别是ie中。所以DOM 操作是非常“昂贵”的
- js对象对比特别快,毕竟js可以写后端,效率还是可以的,所以将 DOM 对比操作放在 JS 层,提高效率、提高重绘性能
通常用三个属性去描述dom结构,比如:标签名(tag)、属性(props)和子元素对象(children),不同的框架对这三个属性的命名会有点差别,但表达的意思是一致的。
<div>
Hello World!
<div id="div1" data-idx={1}>first</div>
<div id="div2">second</div>
</div>vm对应结构如下:
{
"tag": "div",
"props":{},
"children": [
"Hello World!",
{
"tag": "div",
"props":
{
"id": "div1",
"data-idx": 1
},
"children": ["first"]
},
{
"tag": "div",
"props":
{
"id": "div2"
},
"children": ["second"]
}
]
}
二、Virtual DOM的必要性
如下案例:初次渲染render(data),点击change按钮后修改data数组中的数据,只是修改了两个,但是每次都是“推倒重来”=》?旧的dom结构删掉,插入新的dom结构
以下已经进行了优化,即一次性插入dom节点,而不是一行行tr插入
<body>
<div id="container"></div>
<div id="btn-change">change</div>
<script src="./jquery-3.2.1.js"></script>
<script>
var data = [
{
name: "张三",
age: 20,
address: "北京",
},
{
name: "李四",
age: 21,
address: "上海",
},
{
name: "王五",
age: 22,
address: "广州",
},
];
function render(data) {
var $container = $("#container");
$container.html("");
var $table = $("<table>");
$table.append($("<tr><td>name</td><td>age</td><td>address</td></tr>"));
data.forEach((element) => {
$table.append(
$(
`<tr><td>${element.name}</td><td>${element.age}</td><td>${element.address}</td></tr>`
)
);
});
$container.append($table);
}
$("#btn-change").click(function () {
data[1].age = 30;
data[2].address = "深圳";
render(data);
});
render(data);
</script>
</body>思考:只是修改了两个数据,没必要推到重来,毕竟dom操作是很昂贵的,若只是更新变化的dom,那么性能会提升很多
snabbdom:https://github.com/snabbdom/snabbdom
<body>
<div id="container"></div>
<div id="btn-change">change</div>
<script src="https://cdn.bootcdn.net/ajax/libs/snabbdom/0.7.4/snabbdom.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/snabbdom/0.7.4/snabbdom-class.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/snabbdom/0.7.4/snabbdom-props.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/snabbdom/0.7.4/snabbdom-style.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/snabbdom/0.7.4/snabbdom-eventlisteners.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/snabbdom/0.7.4/h.js"></script>
<script>
var snabbdom = window.snabbdom;
var patch = snabbdom.init([
snabbdom_class,
snabbdom_props,
snabbdom_style,
snabbdom_eventlisteners,
]);
var h = snabbdom.h;
var data = [
{
name: "姓名",
age: "年龄",
address: "地址",
},
{
name: "张三",
age: 20,
address: "北京",
},
{
name: "李四",
age: 21,
address: "上海",
},
{
name: "王五",
age: 22,
address: "广州",
},
];
var vnode;
var container = document.getElementById("container");
function render(data) {
var newVnode = h(
"table",
{},
data.map(function (item) {
var tds = [];
for (let i in item) {
if (item.hasOwnProperty(i)) {
tds.push(h("td", {}, item[i] + ""));
}
}
return h("tr", {}, tds);
})
);
if (vnode) {
patch(vnode, newVnode);
} else {
patch(container, newVnode);
}
vnode = newVnode;
}
document.getElementById("btn-change").onclick = function () {
data[1].age = 30;
data[2].address = "深圳";
render(data);
};
render(data);
</script>
</body>如下当点击按钮时,只是更新了变化的dom而不像jquery那样“推倒重来”

Virtual DOM优点:
- Virtual DOM 最大的特点是将页面的状态抽象为 JS 对象的形式,配合不同的渲染工具,使跨平台渲染成为可能。如 React 就借助 Virtual DOM 实现了服务端渲染、浏览器渲染和移动端渲染等功能。
- 在进行页面更新的时候,借助Virtual DOM,DOM 元素的改变可以在内存中进行比较,再结合框架的事务机制将多次比较的结果合并后一次性更新到页面,从而有效地减少页面渲染的次数,提高渲染效率。
如下页面的更新一般会经过几个阶段:

从上面可以看出页面的呈现会分以下3个阶段:
- JS计算
- 生成渲染树
- 绘制页面
JS计算用了935毫秒,生成渲染树143毫秒,绘制60毫秒。如果能有效的减少生成渲染树和绘制所花的时间,更新页面的效率也会随之提高。通过Virtual DOM的比较,我们可以将多个操作合并成一个批量的操作,从而减少dom重排的次数,进而缩短了生成渲染树和绘制所花的时间。
三、如何实现Virtual DOM与真实DOM的映射
借助JSX编译器,可以将文件中的HTML转化成函数的形式,然后再利用这个函数生成Virtual DOM。看下面这个例子:
//index.js
function view() {
return (
<div>
Hello World!
<div id="div1" data-ids="{1}">
first
</div>
<div id="div2">second</div>
</div>
);
}通过babel的plugin(transform-react-jsx)编译后,可以生成如下代码:babel ./src/index.js -o ./public/vm_bundle.js(babel ./src/index.js --out-file ./public/vm_bundle.js)
//vm_bundle.js
function view() {
return h(
"div",
null,
"Hello World!",
h("div", { id: "div1", "data-ids": "{1}" }, "first"),
h("div", { id: "div2" }, "second")
);
}这里的h是一个函数,可以起任意的名字-类似于snabbdom的h函数。这个名字通过babel进行配置:
// 安装npm包: npm install --save-dev babel-cli babel-plugin-transform-react-jsx
// babel-cli 是babel的命令行工具,需要将原始的 .js或.jsx 文件编译
// .babelrc
{
"plugins": [
[
"transform-react-jsx",
{
"pragma": "h"
}
]
]
}
接下来,只需要定义h函数,就能构造出Virtual DOM:
//h函数:生成vnode
function flatten(children) {
return [].concat.apply([], children);
}
function h(tag, props, ...children) {
return {
tag,
props: props || {},
children: flatten(children) || [],
};
}初次渲染,生成vdom后,要基于Virtual DOM,生成真实的DOM--相当于实现snabbdom的patch函数:
//patch函数:vnode=》真实dom
function setProps(element, props) {
for (key in props) {
if (props.hasOwnProperty(key)) {
element.setAttribute(key, props[key]);
}
}
}
function createElement(vdom) {
const t = typeof vdom;
if (t === 'string' || t === 'number') {
return document.createTextNode(vdom);
}
const {tag, props, children} = vdom;
// 1. 创建元素
const element = document.createElement(tag);
// 2. 属性赋值
setProps(element, props);
// 3. 创建子元素
// appendChild在执行的时候,会检查当前的this是不是dom对象,因此要bind一下
children.map(createElement).forEach(element.appendChild.bind(element));
//children.map(createElement).forEach((child) => {
// element.appendChild(child);
//});
return element;
}
最后,将生成好的dom挂载到指定的节点上:
//真实dom插入页面
function renderDOM(vdom, container) {
container.appendChild(createElement(vdom));
}
renderDOM(view(), document.getElementById("root"));如下可以通过一个html来显示最终成果:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app"></div>
<script src="vm_bundle.js"></script>
</body>
</html>展示效果如下:

五、总结
本文介绍了Virtual DOM的基本概念,并讲解了如何利用JSX编译HTML标签,然后生成Virtual DOM,进而创建真实dom的过程。
项目源码:
https://github.com/qingye/VirtualDOM-Study/tree/master/VirtualDOM-Study-01
边栏推荐
- 标准|中国支付清算协会发布首个隐私计算金融规范
- Self attention mechanism
- Mqtt second session -- emqx high availability cluster implementation
- 来个小总结吧
- The difference and usage of JS for in loop and for of loop
- [redis] redis6 learning framework ideas and details
- Oracle subquery
- 乘法器设计(流水线)verilog code
- P4769-[NOI2018]冒泡排序【组合数学,树状数组】
- verilog 归约操作符
猜你喜欢

2022年7月产品经理认证招生简章(NPDP)

Résumé des différentes séries (harmoniques, géométriques)

Wallpaper applet source code double ended wechat Tiktok applet

Heavyweight released "FISCO bcos application landing guide"

今天让你知道PMP考试通过率达97%,可信不可信

How to recover data loss of USB flash disk memory card

闭关修炼(二十)如何做好单元测试

uni-app获取当前页面路由url

机器人代码生成器之Robcogen使用教程

CDGA|交通行业做好数字化转型的核心是什么?
随机推荐
Notes on key words in the original English work biography of jobs (VIII) [chapter six]
批量处理实验接触角数据-MATLAB分析
2022年7月产品经理认证招生简章(NPDP)
Core development board & debugger
Notes on key words in the original English work biography of jobs (VII) [chapter five]
抽象类、接口
51单片机中断与定时器计数器,基于普中科技HC6800-ESV2.0
First electric shock, so you are such a dragon lizard community | dragon lizard developer said that issue 8
ThreadLocal线程变量
How to recite words in tables
Did you really make things clear when you were promoted or reported?
Self attention mechanism
来个小总结吧
Batch processing of experimental contact angle data matlab analysis
今年的网络安全“体检”你做了吗?
Development tips - Image Resource Management
Leetcode(142)——环形链表 II
积分商城运营要如何做才能获取到利润
启牛学堂让开的证券账户是真的安全靠谱吗?
Wallpaper applet source code double ended wechat Tiktok applet