如何对强制重排的 Javascript 操作进行分组?

Posted

技术标签:

【中文标题】如何对强制重排的 Javascript 操作进行分组?【英文标题】:How can I group Javascript actions that force reflow? 【发布时间】:2021-10-11 00:11:15 【问题描述】:

我有一个项目负责管理元素的渲染,但我遇到了性能问题,替换元素然后专注于之前关注的内容。

以下是复制性能问题的最小示例:

const renderPage = () => 
  // get the old section element
  const oldSection = document.querySelector('section')

  // create a new section element (we'll replaceWith later)
  const newSection = document.createElement('section')
  
  // create the render button
  const newButton = document.createElement('button')
  newButton.innerhtml = 'Render Page'
  newButton.onclick = renderPage
  newSection.appendChild(newButton)
  
  // create a bunch of elements
  const dummyDivs = [...new Array(100000)].forEach(() => 
    const dummy = document.createElement('div')
    dummy.innerHTML = 'dummy'
    newSection.appendChild(dummy)
  )
  
  // replace the old page with the new one (causes forced reflow)
  oldSection.replaceWith(newSection)
  // reattach focus on the button (causes forced reflow)
  newButton.focus()


window.renderPage = renderPage
<section>
  <button onclick="renderPage()">Render</button>
</section>

在本地运行时,我在 Chrome/Edge 的性能报告中看到以下内容

replaceWithfocus 都在触发强制回流。有没有办法对这些操作进行批处理或分组,以便只发生一次回流?我意识到根本没有办法真正解决这种情况,但如果我可以批量处理它们,我认为这可能会提高我的性能。

【问题讨论】:

【参考方案1】:

确实,焦点总是会导致回流:What forces layout / reflow

所以你可以做的是通过独立插入新按钮来减少回流时间,启动焦点,然后你可以附加其他孩子:

工作示例:Example

const renderPage = () => 
  // get the old section element
  const oldSection = document.querySelector('section')

  // create a new section element (we'll replaceWith later)
  const newSection = document.createElement('section')
  
  // create the render button
  const newButton = document.createElement('button')
  newButton.innerHTML = 'Render Page'
  newButton.onclick = renderPage
  newSection.appendChild(newButton)
  
  // create a bunch of elements
  const dummies = []; //  store in seperate array
  const dummyDivs = [...new Array(100000)].forEach(() => 
    const dummy = document.createElement('div')
    dummy.innerHTML = 'dummy';
    dummies.push(dummy)
  )
  //  insert new section only with new button
  oldSection.replaceWith(newSection)
  newButton.focus(); // always causes reflow; but fast because it's only one element
  //  store all other nodes after focus
  newSection.append(...dummies)


window.renderPage = renderPage

【讨论】:

感谢@coyer,这利用了渲染按钮始终位于顶部这一事实,在我的情况下从技术上讲这不是真的,但我没有理由不能拆分DOM 分为可聚焦事物之前、可聚焦事物和可聚焦事物之后。这可能比我想要的更符合逻辑,但这是我能弄清楚的。 奇怪的是,我注意到两个示例的运行时间大致相同。您在运行时是否注意到重大变化?它不再有“强制回流可能是性能瓶颈。”,所以显然有些不同(并且在没有更好的答案的情况下,值得被接受),但我很好奇这是否只是不触发警告由于一些技术性问题。 我只测量了附加和聚焦新节点的时间,这在我的示例中几乎快了两倍,并且还检查了只有预期的小回流和较大的回流来完全回流。

以上是关于如何对强制重排的 Javascript 操作进行分组?的主要内容,如果未能解决你的问题,请参考以下文章

FineReport报表开发工具中,如何对行进行强制分页,但是列全部显示?

DOM操作增删改查

java并发编程之volatile关键字

如何等待浏览器完成javascript中的重排?

[LeetCode] 143. 重排链表

更改 CSS 时强制浏览器触发重排