大家好,我是半夏,一个刚刚开始写文的沙雕程序员.如果喜欢我的文章,可以关注 点赞 加我微信:frontendpicker,一起学习交流前端,成为更优秀的工程师~关注公众号:搞前端的半夏,了解更多前端知识,回复 ”网站模板“,免费送N++网站模板!!点我探索新世界!

什么是DOM

DOM(文档对象模型)是一种树状结构,包含有关 HTML(或 XML)页面结构的信息。树中的每个单独的节点代表网页上的一个元素。

在 Javascript 中,可以通过window.document对象访问和修改 DOM。让我们看看如何使用 DOM 接口向网页添加元素。

我们有下面HTML模板代码。

<!DOCTYPE html>
<html lang="zh-cn">
<head>
<title>DOM</title>
</head>
<body>
<div id="app"></div>
<script src="./main.js"></script>
</body>
</html>

PS: 写过Vue项目的同学可能比较熟悉,在Vue脚手架生成的项目中,public文件夹下的index.html也是定义了一个div#app.

要通过 DOM 接口更改页面的内容,我们可以执行以下操作:

const app = document.querySelector('#app');

app.innerHTML = `
<h1>Hello from DOM</h1>
`;

首先,我们从 DOM 中获取一个 id 为“app”的元素,然后我们更改该元素的内容。

这种修改DOM的方法,在以前是我们经常使用的,尤其是JQuery时代。这种方式适用于不经常更新UI的小型应用程序,如果我们想要构建一个高响应的网站,这种方法就会出现问题。

JS操作DOM 是很慢的。每次都重新创建整棵树会浪费时间和资源。如果我们想构建一个高反应性的网页,我们需要寻找另一种解决方案。

一种方法是通过比较新旧树来查看哪些元素需要更新。这正是 Virtual DOM 的目标

创建一个虚拟 DOM

在真实的 DOM 中,有一个document.createElement创建新节点的方法。对于我们的虚拟 DOM,我们也需要这样一个方法。

view方法

让我们创建一个名为h(约定)的函数

const h = (type, props={}, children=[]) => ({
type,
props,
children,
});
  • type参数描述了 HTML 元素的类型,例如h1div等等...

  • props参数的工作方式与 React/Vue 中的 props 完全相同——它允许我们将数据(属性)传递给元素

  • children当前元素内其他子节点。

让我们看看它是如何使用的。


const view = () =>
h('div', {}, [
h('h1', {}, ['Hello']),
h('p', {}, ['from virtual DOM!']),
h('p', {}, ['from virtual DOM!']),
h('p', {}, ['from virtual DOM!']),
h('p', {}, ['from virtual DOM!']),
]);

我们创建了一个div元素,里面有h1p元素。这些元素中的每一个都有一个文本节点作为其子节点。

现在是时候将这个虚拟树转换为实际的 DOM。

render方法

让我们实现一个render功能。


const render = (root, view) => {
const rendered = view(); diff(root, null, rendered);
}; const diff = (root, oldNode, newNode, index) => {
// 判断节点是否变化,有变化则更新 }; render(app, view);

渲染函数首先调用view函数,然后运行 diff 函数,该函数接受一个根元素(来自真实 DOM)、旧的虚拟节点(因为我们第一次渲染它是null)和新的虚拟节点 。

diff方法

基本上,该diff函数只会将 oldNode 与 newNode 进行比较,看看它是否需要更新root.

现在让我们看看如何实现 diff 函数。

const diff = (root, oldNode, newNode, index) => {
// 判断节点是否变化,有变化则更新
if (!oldNode) {
root.appendChild(createElement(newNode));
}
};

如果没有oldNode,我们需要创建这个元素并将其插入 DOM。首先,我们使用该函数创建一个元素createElement,然后我们在第二个实现该函数,然后我们appendChild在一个真实的 DOM 元素上使用该方法,将该节点附加为其子节点。

让我们实现createElement功能。

const createElement = (node) => {
if (typeof node === 'string') {
return document.createTextNode(node);
} const el = document.createElement(node.type);
node.children.map(createElement).forEach(el.appendChild.bind(el)); return el;
};

如果一个节点是一个文本节点(例如“Hello”),我们只需使用document.createTextNode函数渲染它。

如果不是,我们创建给定类型的元素,document.createElement然后循环遍历它的每个子元素,createElement递归调用函数。这样我们就创建了整个树并返回它。

让我们看看到目前为止我们编写的完整代码:


const app = document.querySelector('#app'); const h = (type, props = {}, children = []) => ({
type,
props,
children,
}); const view = () =>
h("div", {}, [
h("h1", {}, ["Hello"]),
h("p", {}, ["from virtual DOM!"]),
h("p", {}, ["from virtual DOM!"]),
h("p", {}, ["from virtual DOM!"]),
h("p", {}, ["from virtual DOM!"]),
]); const render = (root, view) => {
const rendered = view(); diff(root, null, rendered);
}; const diff = (root, oldNode, newNode, index) => {
// 判断节点是否变化,有变化则更新
if (!oldNode) {
root.appendChild(createElement(newNode));
}
}; const createElement = (node) => {
if (typeof node === 'string') {
return document.createTextNode(node);
} const el = document.createElement(node.type);
node.children.map(createElement).forEach(el.appendChild.bind(el)); return el;
};
render(app, view);

现在,在浏览器中,我们可以检查我们的应用程序是否正常工作 - 如果我们运行此代码,我们将看到以下内容:

结论

耶。现在使用viewh函数,我们可以构建无限复杂的 UI。

当然,我们还没有实现状态管理,所以我们不能改变 DOM 中的任何东西。而且我们没有将任何属性传递给 DOM,因此我们无法真正设置应用程序的样式。这个我们会在下一篇文章中继续实现!

手撸一个虚拟DOM,不错的更多相关文章

  1. 手写一个虚拟DOM库,彻底让你理解diff算法

    所谓虚拟DOM就是用js对象来描述真实DOM,它相对于原生DOM更加轻量,因为真正的DOM对象附带有非常多的属性,另外配合虚拟DOM的diff算法,能以最少的操作来更新DOM,除此之外,也能让Vue和 ...

  2. 放弃antd table,基于React手写一个虚拟滚动的表格

    缘起 标题有点夸张,并不是完全放弃antd-table,毕竟在react的生态圈里,对国人来说,比较好用的PC端组件库,也就antd了.即便经历了2018年圣诞彩蛋事件,antd的使用者也不仅不减,反 ...

  3. 使用Java Socket手撸一个http服务器

    原文连接:使用Java Socket手撸一个http服务器 作为一个java后端,提供http服务可以说是基本技能之一了,但是你真的了解http协议么?你知道知道如何手撸一个http服务器么?tomc ...

  4. 【手撸一个ORM】MyOrm的使用说明

    [手撸一个ORM]第一步.约定和实体描述 [手撸一个ORM]第二步.封装实体描述和实体属性描述 [手撸一个ORM]第三步.SQL语句构造器和SqlParameter封装 [手撸一个ORM]第四步.Ex ...

  5. 第二篇-用Flutter手撸一个抖音国内版,看看有多炫

    前言 继上一篇使用Flutter开发的抖音国际版 后再次撸一个国内版抖音,大部分功能已完成,主要是Flutter开发APP速度很爽,  先看下图 项目主要结构介绍 这次主要的改动在api.dart 及 ...

  6. 通过 Netty、ZooKeeper 手撸一个 RPC 服务

    说明 项目链接 微服务框架都包括什么? 如何实现 RPC 远程调用? 开源 RPC 框架 限定语言 跨语言 RPC 框架 本地 Docker 搭建 ZooKeeper 下载镜像 启动容器 查看容器日志 ...

  7. C#基于Mongo的官方驱动手撸一个Super简易版MongoDB-ORM框架

    C#基于Mongo的官方驱动手撸一个简易版MongoDB-ORM框架 如题,在GitHub上找了一圈想找一个MongoDB的的ORM框架,未偿所愿,就去翻了翻官网(https://docs.mongo ...

  8. 如何快速实现一个虚拟 DOM 系统

    虚拟 DOM 是目前主流前端框架的技术核心之一,本文阐述如何实现一个简单的虚拟 DOM 系统. 为什么需要虚拟 DOM? 虚拟 DOM 就是一棵由虚拟节点组成的树,这棵树展现了真实 DOM 的结构.这 ...

  9. 手撸一个SpringBoot-Starter

    1. 简介 通过了解SpringBoot的原理后,我们可以手撸一个spring-boot-starter来加深理解. 1.1 什么是starter spring官网解释 starters是一组方便的依 ...

随机推荐

  1. 74CMS 3.0 存储型XSS漏洞

    一. 启动环境 1.双击运行桌面phpstudy.exe软件 2.点击启动按钮,启动服务器环境 二.代码审计 1.双击启动桌面Seay源代码审计系统软件 2.因为74CMS3.0源代码编辑使用GBK编 ...

  2. 设置一段文字的大小为6px?

    谷歌最小12px, 其他浏览器可以更小 通过transform: scale实现

  3. SpringBoot的 Actuator 是做什么的?

    本质上,Actuator 通过启用 production-ready 功能使得 SpringBoot 应用程序变得更有生命力.这些功能允许我们对生产环境中的应用程序进行监视和管理. 集成 Spring ...

  4. Spring框架的事务管理有哪些优点?

    它为不同的事务API  如 JTA,JDBC,Hibernate,JPA 和JDO,提供一个不变的编程模式. 它为编程式事务管理提供了一套简单的API而不是一些复杂的事务API如 它支持声明式事务管理 ...

  5. redis 是什么?都有哪些使用场景?

    一.什么是redis 首先要说redis,应该先说一下nosql,NoSQL(NoSQL = Not Only SQL ),意即"不仅仅是SQL",泛指非关系型的数据库.随着互联网 ...

  6. Redis 的持久化机制是什么?各自的优缺点?

    Redis 提供两种持久化机制 RDB 和 AOF 机制: 1.RDBRedis DataBase)持久化方式: 是指用数据集快照的方式半持久化模式) 记录 redis 数据库的所有键值对,在某个时间 ...

  7. springboot-访问数据库

    在springboot中,默认的JPA实现是Hibernate,JPA是Java Persistence API的简称,中文名Java持久层API <!--数据库--> <depen ...

  8. Spark学习摘记 —— Pair RDD转化操作API归纳

    本文参考 参考<Spark快速大数据分析>动物书中的第四章"键值对操作",由于pair RDD的一些特殊操作,没有和前面两篇的API归纳放在一起做示例 前面的几个api ...

  9. Python - 分支循环、可迭代对象与迭代器

  10. SVN回滚步骤