大家好,我是半夏,一个刚刚开始写文的沙雕程序员.如果喜欢我的文章,可以关注 点赞 加我微信: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. WebGPU 工具分享 - WGSL 代码高亮插件(VSCode)与预处理工具

    WGSL 还在积极讨论中,虽然各位大佬不是很满意这个新生儿. 不过,社区已经有了基础的实验性工具(VSCode 插件),并支持了较新的语法. ① WGSL 插件 这个插件支持对文件扩展名为 .wgsl ...

  2. loj536「LibreOJ Round #6」花札(二分图博弈)

    loj536「LibreOJ Round #6」花札(二分图博弈) loj 题解时间 很明显是二分图博弈. 以某个点为起点,先手必胜的充要条件是起点一定在最大匹配中. 判断方法是看起点到该点的边有流量 ...

  3. idea执行maven命令的三种方式

    前言: java开发的IDE工具idea默认会提供maven生命周期的图形化执行,但是如果我们需要定制化的执行命令的时候,就需要使用手动执行maven命令的方式,今天就和大家讲一下idea手动执行ma ...

  4. 为什么线程通信的方法 wait(), notify()和 notifyAll()被定 义在 Object 类里?

    Java 的每个对象中都有一个锁(monitor,也可以成为监视器) 并且 wait(),notify() 等方法用于等待对象的锁或者通知其他线程对象的监视器可用.在 Java 的线程中 并没有可供任 ...

  5. 说几个 zookeeper 常用的命令?

    常用命令:ls get set create delete 等.

  6. 用 Java 实现阻塞队列 ?

    参考 java 中的阻塞队列的内容吧,直接实现有点烦

  7. 说出 5 个 JDK 1.8 引入的新特性?

    Java 8 在 Java 历史上是一个开创新的版本,下面 JDK 8 中 5 个主要的特性: Lambda 表达式,允许像对象一样传递匿名函数 Stream API,充分利用现代多核 CPU,可以写 ...

  8. 使用 Spring 通过什么方式访问 Hibernate?

    在 Spring 中有两种方式访问 Hibernate:控制反转 Hibernate Template 和 Callback.继承 HibernateDAOSupport 提供一个 AOP 拦截器.

  9. 详解 IOC

    什么是IOC: IOC-Inversion Of Control,即"控制反转",不是什么技术,而是一种设计思想.在Java开发中,IOC意味着将你设计好的对象交给容器控制,而不是 ...

  10. linux java7升级到java8

    转自:https://blog.csdn.net/u010199866/article/details/81744382 linux java7升级到java8   版权 1.第一步先卸载所有老的jd ...