大家好,我是半夏,一个刚刚开始写文的沙雕程序员.如果喜欢我的文章,可以关注 点赞 加我微信: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. [happyctf]部分writeup

    题目名称:sqltest所属:MISC考察点:盲注 眼力 耐心(好吧是废话) 附件下载下来 ,到手一个流量包,用wireshark打开,大致浏览了一下,抓的应该是盲注的数据流量. 这里有一个经验问题, ...

  2. Linux下使用压力测试工具stress

    一:stress的安装 首先解压安装包到/usr/local/src/下 mv stress-1.0.4.tar.gz /usr/local/src​tar -zxf stress-1.0.4.tar ...

  3. Java代码查错部分?

    1. abstract class Name { private String name; public abstract boolean isStupidName(String name) {} } ...

  4. springboot使用@Value注入properties文件中的值,中文乱码

    最近开发一个需求,讲一个中文值配置在properties文件中,然后代码中使用@Value注解进行注入使用,然而出现了如下状况: 中文出现乱码,将代码修改如下: String str = new St ...

  5. spring-boot-learning-Web开发-深入理解springMVC

    处理器映射 11spring启动阶段就会将@RequestMapping所配置的内容保存到处理器映射HandlerMapping机制中去 22等待请求,通过拦截器拦截请求信息与HandlerMappi ...

  6. Mock 或 Stub 有什么区别?

    存根 一个有助于运行测试的虚拟对象. 在某些可以硬编码的条件下提供固定行为. 永远不会测试存根的任何其他行为. 例如,对于空堆栈,您可以创建一个只为 empty()方法返回 true 的存根.因此, ...

  7. Mybatis 开发 dao 的方法

    1.分析SqlSession使用范围 1.1.SqlSessionFactoryBuilder 通过 SqlSessionFactoryBuilder 创建会话工厂 SqlSessionFactory ...

  8. HTML5标签速查

    HTML5标签速查,助你快速了解HTML 5. HTML 5新加入的标签以黑体标识,HTML 5不支持的以斜体标识. 标签 描述 <!--...--> 评论 <!DOCTYPE> ...

  9. Qunee for HTML5 v1.6新版本发布

    Qunee for HTML5 V1.6正式发布,修复了一些 BUG,增加了滚动条支持,改进了编辑器,增加了JSON 导入导出.告警冒泡.连线流动,UI 定制等扩展示例,欢迎 访问 导航面板 增加了滚 ...

  10. translate3d 对 z-index 居然有影响

    在 Mobile 端需要注意. 安卓 默认浏览器 当中如果 div1 div2 如果 div1 有 translate3d 而 div2 没有 那么 div2 的 z-index 会无效. 解决办法: ...