关于前端:手撸一个虚拟DOM不错

32次阅读

共计 3144 个字符,预计需要花费 8 分钟才能阅读完成。

大家好,我是半夏👴,一个刚刚开始写文的沙雕程序员. 如果喜爱我的文章,能够关注➕ 点赞 👍 加我微信: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,因而咱们无奈真正设置应用程序的款式。这个咱们会在下一篇文章中持续实现!

正文完
 0