初探虚拟 DOM

栏目: JavaScript · 发布时间: 5年前

内容简介:如果有这么一张表格要你维护。后续涉及到表的增删改,你会怎么做?表格简单的时候还好,用 JavaScript 操作起来还算方便。但随着应用越来越复杂,需要处理的数据也越来越大,越来越复杂的时候,需要利用 JavaScript 操作的地方也会越来越多,这个时候准确地修改数据就变得不是那么容易了。

如果有这么一张表格要你维护。

初探虚拟 DOM

后续涉及到表的增删改,你会怎么做?

  • 增:先找到正确的位置,再插元素进去?
  • 删:找到正确的元素,删掉它?
  • 改:找到正确的元素,修改它?

表格简单的时候还好,用 JavaScript 操作起来还算方便。但随着应用越来越复杂,需要处理的数据也越来越大,越来越复杂的时候,需要利用 JavaScript 操作的地方也会越来越多,这个时候准确地修改数据就变得不是那么容易了。

虚拟 DOM 的产生

针对前面的情况,那么能不能用一个东西来存储页面的视图状态,当视图状态发送变化时,读取这个东西,然后更新页面?

比如这一段 HTML 代码对应的 DOM,

<div>
  <div>
    <span>hello</span>
  </div>
  <span>world</span>
</div>
复制代码

我们用另外的一个对象来表示它

let nodesData = {
  tag: 'div',
  children: [
    {
      tag: 'div',
      children: [
        {
          tag: 'span',
          children: [
            {
              tag: '#text',
              text: 'hello'
            }
          ]
        }
      ]
    },
    {
      tag: 'span',
        children: [
          {
            tag: '#text',
            text: 'world'
          }
        ]
    }
  ]
}
复制代码

用这个对象来表示 DOM 结构,我们可以根据这个对象来构建真正的 DOM。

现在我们需要写一个函数,将这个虚假的 DOM 转化为真实的 DOM。

化假为真

function vNode({tag, children, text}){
  this.tag = tag
  this.children = children
  this.text = text
}

vNode.prototype.render = function(){
  if(this.tag === '#text'){
    return document.createTextNode(this.text)
  }
  
  let el = document.createElement(this.tag)
  this.children.map((vChild) => {
    el.appendChild(new vNode(vChild).render())
  })
  
  return el
}
复制代码

调用上面的这个函数可以将我们用来表示 DOM 的对象(虚假 DOM)变成真正的 DOM。

let node = new vNode(nodesData)
node.render()
复制代码

这样,就化假 DOM 为真 DOM 了。

当我们的需要改变 DOM 时,只需要改变其对应的虚假 DOM,再调用一下 render 函数,就可以改变真实 DOM,不需要我们亲自用 JavaScript 去操作页面中的 DOM。

局部更新

上面虽然实现了从虚假 DOM 到真实 DOM 的转化,但是也有一个问题,那就是每次转化都会遍历所有的 DOM 结构,通通的全部转化一遍。如果只有一个小地方发生了改变,也需要将全部的 DOM 更新一遍,那这样就太耗费性能了,我们应该比较虚假 DOM 的变化,只更新变化的地方。

function patchElement(parent, newVNode, oldVNode, index = 0) {
  if (!oldVNode) {
    parent.appendChild(newVNode.render());
  } else if (!newVNode) {
    parent.removeChild(parent.childNodes[index]);
  } else if (newVNode.tag !== oldVNode.tag || newVNode.text !== oldVNode.text) {
    parent.replaceChild(new vNode(newVNode).render(), parent.childNodes[index]);
  } else {
    for (
      let i = 0;
      i < newVNode.children.length || i < oldVNode.children.length;
      i++
    ) {
      patchElement(
        parent.childNodes[index],
        newVNode.children[i],
        oldVNode.children[i],
        i
      );
    }
  }
}
复制代码

通过这个算法,逐层比较新旧虚假 DOM 的结构变化,如果没变,就继续往下遍历;如果发现结构发生了变化,就重新生成真实 DOM 替换掉旧的。

来看一看效果。

初探虚拟 DOM

从图中可以看到,当虚假 DOM 发生变化时,在更新真实 DOM 的过程中,只更新了发生了变化的那一部分,没有发生变化的地方是没动的,这样就优化了性能。

结语

这是一个非常粗糙的实现,diff 算法非常简单地比较了差异,这里仅仅表达了一下虚拟 DOM 的实现思想,在实际运用过程还有很多地方需要考虑。

这里贴个完整代码。

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>

<body>
  <div id="test"></div>
  <script>
    let nodesData = {
      tag: 'div',
      children: [
        {
          tag: 'div',
          children: [
            {
              tag: 'span',
              children: [
                {
                  tag: '#text',
                  text: 'hello'
                }
              ]
            }
          ]
        },
        {
          tag: 'span',
          children: [
            {
              tag: '#text',
              text: 'world'
            }
          ]
        }
      ]
    };
    let nodesData2 = {
      tag: 'div',
      children: [
        {
          tag: 'div',
          children: [
            {
              tag: 'span',
              children: [
                {
                  tag: '#text',
                  text: 'HELLO'
                }
              ]
            }
          ]
        },
        {
          tag: 'span',
          children: [
            {
              tag: '#text',
              text: 'WORLD'
            }
          ]
        }
      ]
    };

    function vNode({ tag, children, text }) {
      this.tag = tag;
      this.children = children;
      this.text = text;
    }

    vNode.prototype.render = function () {
      if (this.tag === '#text') {
        return document.createTextNode(this.text);
      }

      let el = document.createElement(this.tag);

      this.children.map(vChild => {
        el.appendChild(new vNode(vChild).render());
      });

      return el;
    };

    function patchElement(parent, newVNode, oldVNode, index = 0) {
      if (!oldVNode) {
        parent.appendChild(newVNode.render());
      } else if (!newVNode) {
        parent.removeChild(parent.childNodes[index]);
      } else if (newVNode.tag !== oldVNode.tag || newVNode.text !== oldVNode.text) {
        parent.replaceChild(new vNode(newVNode).render(), parent.childNodes[index]);
      } else {
        for (
          let i = 0;
          i < newVNode.children.length || i < oldVNode.children.length;
          i++
        ) {
          patchElement(
            parent.childNodes[index],
            newVNode.children[i],
            oldVNode.children[i],
            i
          );
        }
      }
    }

    let node1 = new vNode(nodesData);
    let node2 = new vNode(nodesData2);

    let test = document.querySelector('#test');
    test.appendChild(node1.render());

    setTimeout(() => {
      patchElement(test, node2, node1, 0);
    }, 5000);
  </script>
</body>

</html>
复制代码

以上所述就是小编给大家介绍的《初探虚拟 DOM》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

编程卓越之道

编程卓越之道

海德 / 张菲 / 电子工业出版社 / 2007-4 / 69.00元

《编程卓越之道第二卷:运用底层语言思想编写高级语言代码》是《编程卓越之道》系列书的第二卷,将探讨怎样用高级语言(而非汇编语言)编程得到高效率机器代码。在书中,您可以学到如何分析编译器的输出,以便检验代码的所作所为,从而得到高质量的机器码;了解编译器为常见控制结构生成的典型机器指令,以便在编写高级语言程序时选用恰当的语句;掌握编译器将各种常量和变量类型转换成机器数据的方法,以便于使用这些数据写出又快......一起来看看 《编程卓越之道》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

URL 编码/解码
URL 编码/解码

URL 编码/解码

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具