当前位置:网站首页>手写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
边栏推荐
- 2022年7月系统集成项目管理工程师认证招生简章
- Simple use of vlookup function in Excel -- exact matching or approximate matching data
- Tutorial on building open source Internet of things platform
- Notes on key words in the original English work biography of jobs (VIII) [chapter six]
- How to recite words in tables
- sql server 用 administrator 权限运行吗?还是以普通用户运行呢?
- Is it safe for the top ten securities companies to open accounts? Is it reliable?
- The sixth season of 2022 perfect children's model Qingyuan competition area audition came to a successful conclusion
- 802.11--802.11n protocol phy
- Uber前安全主管面临欺诈指控 曾隐瞒数据泄露事件
猜你喜欢

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

2022年7月系统集成项目管理工程师认证招生简章

Does the SQL server run with administrator privileges? Or run it as a normal user?

Chengtong network disk imitation blue playing network disk source code with video tutorial

51单片机中断与定时器计数器,基于普中科技HC6800-ESV2.0

Actual combat memoir starts from webshell to break through the border

Differences between x86 and x64

2022 spring summer collection koreano essential reshapes the vitality of fashion

Uber前安全主管面临欺诈指控 曾隐瞒数据泄露事件
Working for many years, recalling life -- three years in high school
随机推荐
在 RedisTemplate 中使用 scan
[untitled]
La finale de la zone de compétition Hefei de la sixième saison 2022 a été couronnée de succès.
Sorting out easily confused words in postgraduate entrance examination English 【 flash 】
2022第六季完美童模 合肥賽區 决賽圓滿落幕
编程语言
闭关修炼(二十)如何做好单元测试
Batch processing of experimental contact angle data matlab analysis
A high-frequency problem, three kinds of model thinking to solve this risk control problem
Huawei equipment is configured with medium-sized network WLAN basic services
Résumé des différentes séries (harmoniques, géométriques)
ES6 data type map & set
观察者模式怎么实现
Uber前安全主管面临欺诈指控 曾隐瞒数据泄露事件
JS获取图片或base64的宽高等基本信息
15 things to learn in a year of internship in famous enterprises, so you can avoid detours.
[microservices openfeign] timeout of openfeign
Verilog 拼接操作符号
Self attention mechanism
闭关修炼(二十四)浅入了解跨域问题