如何快速实现一个虚拟 DOM 系统
虚拟 DOM 是目前主流前端框架的技术核心之一,本文阐述如何实现一个简单的虚拟 DOM 系统。
为什么需要虚拟 DOM?
虚拟 DOM 就是一棵由虚拟节点组成的树,这棵树展现了真实 DOM 的结构。这些虚拟节点是轻量的、无状态的,一般是字符串或者仅仅包含必要字段的 JavaScript 对象。虚拟节点可以被组装成节点树树,通过特定的 "diff" 算法对两个节点树进行对比,找出其中细微的变更点,然后更新到真实 DOM 上去。
之所以会有虚拟 DOM,是因为直接更新真实 DOM 非常昂贵。通过新比对虚拟 DOM,然后只将变化的部分更新到真实 DOM 上去。这么做都是操作纯 JavaScript 对象,尽量避免了直接操作 DOM,读写成本低很多。
如何实现虚拟 DOM
在开始之前,我们需要明确一个虚拟 DOM 系统应该包含哪些必要的组成部分?
首先,我们要定义清楚什么是虚拟节点。一个虚拟节点可以是一个普通 JavaScript 对象,也可以是一个字符串。
我们定义一个函数 createNode
来创建虚拟节点。一个虚拟节点至少包含三个信息:
tag
:保存虚拟节点的标签名,字符串props
:保存虚拟节点的 properties/attributes,普通对象children
:保存虚拟节点的子节点,数组
下面的代码是 createNode
实现样例:
const createNode = (tag, props, children) => ({
tag,
props,
children,
});
我们通过 createNode
可以轻松的创建虚拟节点:
createNode('div', { id: 'app' }, ['Hello World']);
// 返回如下:
{
tag: 'div',
props: { id: 'app' },
children: ['Hello World'],
}
现在,我们需要定义一个 createElement
函数来根据虚拟节点创建真实的 DOM 元素。
在 createElement
中,我们需要创建一个新的 DOM 元素,然后遍历虚拟节点的 props 属性,将其中的属性添加到 DOM 元素上去,之后再遍历 children 属性。如下代码是一个实现样例:
const createElement = vnode => {
if (typof vnode === 'string') {
return document.createTextNode(vnode); // 如果是字符串就直接返回文本元素
}
const el = document.createElement(vnode.tag);
if (vnode.props) {
Object.entries(vnode.props).forEach(([name, value]) => {
el[name] = value;
});
}
if (vnode.children) {
vnode.children.forEach(child => {
el.appendChild(createElement(child));
});
}
return el;
}
现在,我们可以通过 createElement
将虚拟节点转变成真实 DOM 了。
createElement(createNode("div", { id: "app" }, ["Hello World"]));
// 输出: <div id="app">Hello World</div>
我们再来定义一个 diff
函数来实现 'diff' 算法。这个 diff
函数接收三个参数,一个是已经存在的 DOM 元素,一个是旧的虚拟节点,一个是新的虚拟节点。在这个函数中,我们将对比两个虚拟节点,在需要的时候,将旧的元素替换掉。
const diff = (el, oldVNode, newVNode) => {
const replace = () => el.replaceWith(createElement(newVNode));
if (!newVNode) return el.remove();
if (!oldVNode) return el.appendChild(createElement(newVNode));
// 处理纯文本的情况
if (typeof oldVNode === 'string' || typeof newVNode === 'string') {
if (oldVNode !== newVNode) return replace();
} else {
// 对比标签名
if (oldVNode.tag !== newVNode.tag) return replace();
// 对比 props
if (!oldVNode.props?.some((prop) => oldVNode.props?[prop] === newVNode.props?[prop])) return replace();
// 对比 children
[...el.childNodes].forEach((child, i) => {
diff(child, oldVNode.children?[i], newVNode.children?[i]);
});
}
}
在这个函数中,我们先处理纯文本的情况,如果新旧两个字符串不相同,则直接替换。之后,我们就可以假定两个虚拟节点都是对象了。我们先对比两个节点的标签名是否相同,不同则直接替换。之后对比两个节点的 props 是否相同,不同也直接替换。最后我们在递归的使用 diff
函数对比两个虚拟节点的 children。
至此,我们就实现了一个简版虚拟 DOM 系统所必须的所有功能。下面是使用样例:
const oldVNode = createNode("div", { id: "app" }, ["Hello World"]);
const newVNode = createNode("div", { id: "app" }, ["Goodbye World"]);
const el = createElement(oldVNode);
// <div id="app">Hello World</div>
diff(el, oldVNode, newVNode);
// el will become: <div id="app">Goodbye World</div>
文中的实现侧重于展示虚拟 DOM 的实现原理,在实现代码中并未考虑性能等其他因素。
如何快速实现一个虚拟 DOM 系统的更多相关文章
- vue 快速入门 系列 —— 虚拟 DOM
其他章节请看: vue 快速入门 系列 虚拟 DOM 什么是虚拟 dom dom 是文档对象模型,以节点树的形式来表现文档. 虚拟 dom 不是真正意义上的 dom.而是一个 javascript 对 ...
- 手撸一个虚拟DOM,不错
大家好,我是半夏,一个刚刚开始写文的沙雕程序员.如果喜欢我的文章,可以关注 点赞 加我微信:frontendpicker,一起学习交流前端,成为更优秀的工程师-关注公众号:搞前端的半夏,了解更多前端知 ...
- 手动实现一个虚拟DOM算法
发现一个好文:<深度剖析:如何实现一个 Virtual DOM 算法> 源码 文章写得非常详细,仔细看了一遍代码,加了一些注释.其实还有有一些地方看的不是很懂(毕竟我菜qaq 先码 有时间 ...
- 手写一个虚拟DOM库,彻底让你理解diff算法
所谓虚拟DOM就是用js对象来描述真实DOM,它相对于原生DOM更加轻量,因为真正的DOM对象附带有非常多的属性,另外配合虚拟DOM的diff算法,能以最少的操作来更新DOM,除此之外,也能让Vue和 ...
- PHP7 学习笔记(十一)使用phpstudy快速配置一个虚拟主机
说明:为了windows本地开发php方便,这里推荐使用PHP集成环境phpstudy. 目的:使用域名访问项目(tinywan.test) 1.官网:http://www.phpstudy.net ...
- 虚拟DOM介绍
[转自]:https://www.jianshu.com/p/616999666920 为什么需要虚拟DOM 先介绍浏览器加载一个HTML文件需要做哪些事,帮助我们理解为什么我们需要虚拟DOM.web ...
- 虚拟dom?diff算法?key?Vue原理的核心三问?打包教你搞定。
为什么需要虚拟DOM 先介绍浏览器加载一个HTML文件需要做哪些事,帮助我们理解为什么我们需要虚拟DOM.webkit引擎的处理流程,如下图所示: 所有浏览器的引擎工作流程都差不多,如上图大致分5步: ...
- 2.ReactJS基础(虚拟DOM,JSX语法)
将脚手架(create-react-app)创建的todolist项目精简为hello world示例 即,删除自动生成的样式文件.logo.svt.App.test.js.serviceWorker ...
- 虚拟DOM与DOM diff算法
虚拟DOM是什么? 一个虚拟DOM(元素)是一个一般的js对象, 准确的说是一个对象树(倒立的) 虚拟DOM保存了真实DOM的层次关系和一些基本属性,与真实DOM一一对应,如果只是更新虚拟DOM, 页 ...
随机推荐
- 国产龙芯3A3000处理器评测:与英特尔差距明显
国产龙芯3A3000处理器评测:与英特尔差距明显 国产龙芯3A3000处理器评测:与英特尔差距明显 新浪财经APP缩小字体放大字体收藏微博微信分享579 新酷产品第一时间免费试玩,还有众多优质达人分享 ...
- 原生基础js脚本实现--在线答题系统
全部代码在最下面----需要的直接往下翻 html方面的代码 : 正确的答案 value=s <!DOCTYPE html> <html lang="en"&g ...
- 【进阶之路】多线程条件下分段处理List集合的几种方法
这两个月来因为工作和家庭的事情,导致一直都很忙,没有多少时间去汲取养分,也就没有什么产出,最近稍微轻松了一点,后续的[进阶之路]会慢慢回到正轨. 开门见山的说,第一次接触到多线程处理同一个任务,是使用 ...
- Springboot单元测试@RunWith注解
1.RunWith 注解 RunWith 就是一个运行器 可以在单元测试的时候,自动创建spring的应用上下文 2.正确使用 pom.xml <dependency> <group ...
- Vue&Element 前端应用开发之菜单和路由的关系
我们一般的应用系统,菜单是很多功能界面的入口,菜单为了更好体现功能点的设置,一般都是动态从数据库生成的,而且还需要根据用户角色的不同,过滤掉部分没有权限的菜单:在Vue&Element的纯前端 ...
- css——圣杯布局
圣杯布局要求 header和footer各自占领屏幕所有宽度,高度固定 中间dontainer部分为左中右三栏式布局 三栏布局中左右两侧宽度固定,中间部分自动填充 实现方式 1.浮动 先定义heade ...
- 深度学*点云语义分割:CVPR2019论文阅读
深度学*点云语义分割:CVPR2019论文阅读 Point Cloud Oversegmentation with Graph-Structured Deep Metric Learning 摘要 本 ...
- 开放式神经网络交换-ONNX(下)
开放式神经网络交换-ONNX(下) 计算节点由名称.它调用的算子operator的名称.命名输入的列表.命名输出的列表和属性列表组成. 输入和输出在位置上与算子operator输入和输出相关联.属性通 ...
- CVPR2020:点云弱监督三维语义分割的多路径区域挖掘
CVPR2020:点云弱监督三维语义分割的多路径区域挖掘 Multi-Path Region Mining for Weakly Supervised 3D Semantic Segmentation ...
- MapReduce —— MapTask阶段源码分析(Input环节)
不得不说阅读源码的过程,极其痛苦 .Dream Car 镇楼 ~ ! 虽说整个MapReduce过程也就只有Map阶段和Reduce阶段,但是仔细想想,在Map阶段要做哪些事情?这一阶段具体应该包含数 ...