重新排序 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?对于日常的、现实世界的任务来说不是那么有趣。
我希望这能解决你所有的疑惑。
我正在尝试了解 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?对于日常的、现实世界的任务来说不是那么有趣。
我希望这能解决你所有的疑惑。