重新排序 DOM 个节点时的 HyperHTML 性能

HyperHTML performance when re-ordering DOM Nodes

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

阅读有关 how it works under the hood 的内容,似乎暗示模板与 DOM 之间建立了牢固的联系,我认为这意味着它需要与 Virtual[=29= 不同的思维方式] 优化性能。

我写了一些代码来显示 table 中 N 个元素的排序,使用 hyperHtml 与 normalHtml。 normalHtml 版本只是刷新 table 并重建元素。它们在性能方面似乎相似。我在这里比较苹果和橘子吗?如何让 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 在我的代码示例中的性能与普通 ol' DOM 操作一样,以及我如何才能使 HyperHTML 在重新排序 DOM 节点时更加高效?

更新

hyperHTML 2.14 引入了 domdiff V2 的用法,它带来了 petit-dom 的性能,但库的成本增加了 0.6K,希望值得改变。

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

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

请注意关于旧 snab 的所有内容dom 差异也不再相关。


看起来你可以读得更远一点,达到 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 或从 ASC 切换到 DESC 时,它比它的原始 DOM 对应物快 5 倍,但是当涉及到有序列表时,完全 运行dom 一,domdiff 做了很多检查 DOM 对方根本不关心,所以它可以更慢.

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

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

70000 行毫不费力

这些天我正在研究 hyperHTML 自定义元素,其目的是通过 table 处理数十万行sortable 和 searchable.

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

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

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

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