【问题标题】:HyperHTML performance when re-ordering DOM Nodes重新排序 DOM 节点时的 HyperHTML 性能
【发布时间】:2019-02-11 15:46:28
【问题描述】:

我试图了解HyperHTML 以及如何从中获得最佳性能。

阅读how it works under the hood,似乎暗示模板和 DOM 之间建立了紧密的联系,我认为这意味着它需要与 VirtualDOM 不同的思维方式来优化性能。

我编写了一些代码来显示使用 hyperHtml 与 normalHtml 对表中 N 元素进行排序。 normalHtml 版本只是刷新表并重建元素。它们在性能方面似乎都相似。我在这里比较苹果和橙子吗?如何让 hyperHtml 版本的代码性能更好?

代码:

const numberOfElements = 10000
const items = Array.apply(null, Array(numberOfElements)).map((el, i) => i).sort(() => .5 - Math.random())
const sortMethods = [

  () => 0,
  (a, b) => a - b,
  (a, b) => b - a

]

function hyperHtmlTest() {

  const $node = document.createElement('div')
  const $table = document.createElement('table')
  const $button = document.createElement('button')
  const tableRender = hyperHTML.bind($table)

  let sortMethodIndex = 0

  function render () {

    const sortMethod = sortMethods[sortMethodIndex++ % sortMethods.length]

    tableRender`${
      items.concat().sort(sortMethod).map(item => {
        return `<tr><td>${item}</td></tr>`
      })
    }`
  }

  $node.appendChild($button)
  $node.appendChild($table)

  $button.textContent = 'HyperHTml Sort'
  $button.onclick = render  

  return $node

}

function normalHtmlTest() {

  const $node = document.createElement('div')
  const $table = document.createElement('table')
  const $button = document.createElement('button')

  let sortMethodIndex = 0

  function render () {

    const sortMethod = sortMethods[sortMethodIndex++ % sortMethods.length]

    while($table.childNodes.length)
      $table.removeChild($table.childNodes[0])

    const frag = document.createDocumentFragment()

    items.concat().sort(sortMethod).forEach(item => {

      const tr = document.createElement('tr')
      const td = document.createElement('td')

      td.textContent = item
      tr.appendChild(td)

      frag.appendChild(tr)

    })

    $table.appendChild(frag)

  }

  $node.appendChild($button)
  $node.appendChild($table)

  $button.textContent = 'NormalHtml Sort'
  $button.onclick = render  

  return $node

}

document.body.appendChild(hyperHtmlTest())
document.body.appendChild(normalHtmlTest())

或CodePen

总结一下这个问题:为什么在我的代码示例中,HyperHTML 的性能与普通的 DOM 操作一样高效,以及在重新排序 DOM 节点时如何使 HyperHTML 更高效?

【问题讨论】:

  • 我没用过hyperHTML,但是你应该使用DOM节点来构建它吗?我在the documentation 中没有看到对createElement 的任何引用。看起来像you should be using hyper。
  • @Aankhen 我不支持hyperHtmlTest()。 createElement 的引用只是初始设置。此后的所有渲染都使用带有模板文字的hyperHTML。
  • 哦,对不起。显示我所知道的! 退出
  • @Aankhen 不用担心 :-)

标签: javascript dom template-literals virtual-dom hyperhtml


【解决方案1】:

更新

hyperHTML 2.14 引入了 domdiff V2 的使用,它带来了类似 petit-dom 的性能,但需要为库额外增加 0.6K 的成本,希望值得改变。

由于某些奇怪的原因,原始演示在 Safari 上也存在巨大问题,很可能与事实节点直接作为 TR 附加到 TABLE 中,而不是附加到表 TBODY 元素中有关。

无论如何,您现在可以比较有线演示 through this CodePen 的性能。

请注意,关于旧的 snabdom diffing 的所有内容也不再相关。


看起来你可以读得更远一点,达到the wire part,因为你得到了bind。

基本上,如果您使用字符串数组作为内插值,您所做的与innerHTML 之类的操作没有什么不同,但会产生所有常规副作用:

  • 每次您删除所有节点并从头开始创建它们时
  • 对任何节点的每个引用都将永远丢失
  • GC 操作量更高
  • 布局容易 XSS,因此不安全

为避免复制所有这些innerHTML 陷阱并正确使用hyperHTML,您需要将wire 项添加到这些代表的节点。

tableRender`${
  items.concat().sort(sortMethod).map(item => {
    return wire(item)`<tr><td>${item.text}</td></tr>`
  })
}`

但是,由于内存是一个问题,线路通过WeakMap 工作,因此数字,除非用作线路的 :ids,否则不是很好。

现实情况是,现实世界中没有人将数字或字符串作为项目,99% 的时间都是由对象表示的,所以为了演示,让对象成为现实。

Array.apply(null, Array(numberOfElements)).map(
  (el, i) => new Number(i)
)

一旦你有了对象而不是基元,这是除了我为演示而创建的对象之外,更现实的场景,每次调用渲染或更新时,行都不会被丢弃并且每次都重新创建,这些将被简单地重新排序,正如您在my CodePen fork 中看到的那样,基本上总结如下:

function hyperHtmlTest() {
  const {bind, wire} = hyperHTML;
  const render = bind(document.createElement('div'));
  let sortMethodIndex = 0;
  return update();
  function update() {
    const sortMethod = sortMethods[sortMethodIndex++ % sortMethods.length];
    return render`
    <button onclick=${update}>HyperHTml Sort</button>
    <table>
      ${items.concat().sort(sortMethod).map(
        item => wire(item)`<tr><td>${+item}</td></tr>`
      )}
    </table>`;
  }
}

关于性能

hyperHTML后面有an engine called domdiff,目的是重新组织节点。

domdiff 中使用的算法平均而言非常快,但在某些情况下它可能比浏览器一次创建相同布局要慢。

你可以很容易地在我的笔中发现,当你从 ASC 切换到 DESC 或反之亦然时,它比普通 DOM 对应物快 5 倍,但是当涉及到完全随机的有序列表时,domdiff em> 做了很多检查,DOM 对应方根本不关心,所以它可能会更慢。

简而言之,虽然普通 DOM 方法线性快(或慢),但算法一有最好的情况和最坏的情况。

在几乎所有情况下都表现良好的算法是 petit-dom 使用的算法,但整个逻辑部分的权重在 IMO 中对于现实世界的场景来说是不必要的,但对于非现实世界的基准测试来说肯定是令人印象深刻的。

70000 行不费吹灰之力

这已经不是什么秘密了,我现在正在开发一个 hyperHTML 自定义元素,其目的是通过一个可排序和可搜索的表格来处理数十万行。

您可以see an early prototype in this page,并意识到框架如何处理 10000 个表行并不重要,因为一个好的组件不应该在 DOM 上放置这么多节点,因为没有用户能够看到所有一次。

正如您在 70K 表中看到的那样,排序是一瞬间,搜索和滚动也是如此,这就是 HyperHTMLElement 的全部荣耀。

These benchmarks?对于日常的、现实世界的任务来说不是那么有趣。

我希望这能解答你所有的疑惑。

【讨论】:

  • 感谢您的回复,以及为这个项目所做的工作。我确实阅读了您提到的文档部分,但我不明白bind 和wire 的解释。对我来说,文档从这个主题上转移得太快了——但也许我只是慢了。谢谢你的笔。我added some perf times to the console output。正如您所提到的,为 Hyper 创建 els 的初始速度比标准 DOM 慢,但在那之后它会变得平缓。不过,我仍然看不到 DOM 的显着改进。我错过了什么吗?
  • 从有序 ASC 到有序 DESC(反之亦然)的速度更快,但是当您从有序到随机以及从随机到有序时,您不会更快。这是意料之中的,因为我已经解释过:在您的示例中,DOM 线性快速(或缓慢),但您每次都会丢失节点,因此会导致大量 GC 工作。如果你在现实生活中需要准确的表格,那么没有库会在那里获胜,那就去 vanilla DOM。
  • P.S.在那里使用innerHTML 你可以走得更快......如果这就是你所追求的,也考虑一下。
  • 我听到了。再次感谢。也许,正如您所说,更好的方法是只向用户显示正在显示的内容。我只是想避免使用滚动事件。
  • 如果你有有意义的排序,就像它通常在每个表中一样,你会有更好的性能。在这里弄乱结果的是随机因素,但这通常不是真实世界的场景+用户滚动 10000 行或者您显示 10000,这是否现实?因为要同时加载或显示大量数据,当他们不可避免地只能看到其中的一部分时,即使使用 5K 显示器也是如此。 TL;DR 我不会为这些测试而烦恼,但是如果您认为 hyperHTML 表现不佳,请随意尝试其他库,最后应该是您感到高兴,而不是我 :-)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-30
  • 1970-01-01
  • 2011-05-20
  • 2011-12-25
相关资源
最近更新 更多