手撸一个虚拟DOM|手撸一个虚拟DOM,不错!

大家好,我是半夏,一个刚刚开始写文的沙雕程序员.如果喜欢我的文章,可以关注? 点赞 加我微信:frontendpicker,一起学习交流前端,成为更优秀的工程师~关注公众号:搞前端的半夏,了解更多前端知识,回复 ”网站模板“,免费送N++网站模板!!点我探索新世界!
什么是DOM DOM(文档对象模型)是一种树状结构,包含有关 HTML(或 XML)页面结构的信息。树中的每个单独的节点代表网页上的一个元素。
在 Javascript 中,可以通过window.document对象访问和修改 DOM。让我们看看如何使用 DOM 接口向网页添加元素。
我们有下面HTML模板代码。
DOM

PS: 写过Vue项目的同学可能比较熟悉,在Vue脚手架生成的项目中,public文件夹下的index.html也是定义了一个div#app.
要通过 DOM 接口更改页面的内容,我们可以执行以下操作:
const app = document.querySelector('#app'); app.innerHTML = ` Hello from DOM `;

首先,我们从 DOM 中获取一个 id 为“app”的元素,然后我们更改该元素的内容。
手撸一个虚拟DOM|手撸一个虚拟DOM,不错!
文章图片

这种修改DOM的方法,在以前是我们经常使用的,尤其是JQuery时代。这种方式适用于不经常更新UI的小型应用程序,如果我们想要构建一个高响应的网站,这种方法就会出现问题。
JS操作DOM 是很慢的。每次都重新创建整棵树会浪费时间和资源。如果我们想构建一个高反应性的网页,我们需要寻找另一种解决方案。
一种方法是通过比较新旧树来查看哪些元素需要更新。这正是 Virtual DOM 的目标。
创建一个虚拟 DOM 【手撸一个虚拟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);

现在,在浏览器中,我们可以检查我们的应用程序是否正常工作 - 如果我们运行此代码,我们将看到以下内容:
手撸一个虚拟DOM|手撸一个虚拟DOM,不错!
文章图片

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

    推荐阅读