【问题标题】:Checking width and setting width inside forEach loop performance在 forEach 循环性能中检查宽度和设置宽度
【发布时间】:2018-07-31 09:20:58
【问题描述】:

下面有两段代码

片段#1

const doubleWidth = (element) => {
  const width = element.offsetWidth;
  element.style.width = `${width * 2}px`;
};
button.addEventListener('click', (event) => {
  boxes.forEach(doubleWidth);
});

片段#2

button.addEventListener('click', (event) => {
  var widths = boxes.map(item => item.offsetWidth);
  boxes.forEach((element, index) => {
         element.style.width = `${widths[index] * 2}px`;
  });
});

Snippet #1 有很多 48 毫秒,而 sn-p #2 只有 18.4 毫秒。为什么会有这种行为?

毕竟,我还在做计算和设置(强制回流)两个操作。

这是完整的代码 - https://codepen.io/kushalmahajan/pen/mjXVqp?editors=0010

更新 - 所以,让我再解释一下

在片段 #1 中,我每次都会看到一个模式,例如计算、重置、计算、重置……等等

在片段 #2 中。事实并非如此。

请以渲染管道为基础回答

【问题讨论】:

  • 有趣,看起来区别在于读写访问DOM元素(用chrome测试)
  • 更改 CSS 属性后读取 offsetWidth 将强制重排。所以阅读所有这些,然后设置它们,而不是交替进行。或者根本不从offsetWidth 读取,而只是将存储在.style.width 中的像素值加倍

标签: javascript performance performance-testing


【解决方案1】:

在片段 1 中 每次执行 2 个命令时:

const width = element.offsetWidth;
element.style.width = `${width * 2}px`;

在片段 2 中 每次只执行 1 个命令:

element.style.width = `${widths[index] * 2}px`;

因此,我认为执行时间增加一倍以上是很自然的。取决于执行以下命令需要多少时间:

const width = element.offsetWidth;

为了在 2 个 sn-ps 之间进行公平比较,我建议从循环中删除上述命令,并检查在这种情况下执行 sn-p 1 需要多长时间。

希望对你有帮助。

【讨论】:

  • 我已将 Snippet 1 更改为 element.style.width = `${element.offsetWidth * 2}px`;,但它仍然慢得多 codepen.io/anon/pen/yqvOQM?editors=1011
  • 我认为是变量的计算:doubleWidth 每次都会计算和重置,这就是为什么它没有显示在 sn-p 2 中,这个计算和重置也可能很耗时。
  • 这两个操作都具有恒定的时间复杂度。我不认为这有什么不同
  • 在两个 sn-ps 中都是计算出来的
【解决方案2】:

我不确定原因,但显然如果您先存储offsetWidth,那么无论您使用forEach 还是map,性能方面都没有关系:here's a pen to illustrate this behavior

你可以看到我计时了三个组合:

  1. forEach 立即获得offsetWidth 并设置width
  2. forEach 首先存储offsetWidth 和相应的元素,然后在第二个forEach 中设置宽度
  3. map 存储 offsetWidth 然后在 forEach 循环中的元素上设置 width

选项 2.3. 在性能方面基本相同。由此看来,我想说获取offsetWidth 和设置width 的组合是性能瓶颈。真的不能告诉你更多,对不起!


window.onload = () => {
    const boxes = Array.from(document.querySelectorAll('.box'));

    document.getElementById('double-sizes-forEach')
    .addEventListener('click', (event) => {
        console.time('Double Sizes ForEach');

        boxes.forEach((element, index) => {
        const width = element.offsetWidth;
        element.style.width = `${width * 2}px`;
        });

        console.timeEnd('Double Sizes ForEach');
    });

    document.getElementById('double-sizes-forEach-2')
    .addEventListener('click', (event) => {
        console.time('Double Sizes ForEach 2');

        let a = [];
        boxes.forEach((element, index) => {
        a.push([element, element.offsetWidth]);
        });
        a.forEach(([e, w]) => {
        e.style.width = `${w * 2}px`; 
        });

        console.timeEnd('Double Sizes ForEach 2');
    });

    document.getElementById('double-sizes-map')
    .addEventListener('click', (event) => {
        console.time('Double Sizes Map');

        var widths = boxes.map(item => item.offsetWidth);
        boxes.forEach((element, index) => {
            element.style.width = `${widths[index] * 2}px`;
        });

        console.timeEnd('Double Sizes Map');
    });
};

输出:

Double Sizes ForEach: 12.341064453125ms
Double Sizes ForEach 2: 0.539794921875ms
Double Sizes Map: 0.590087890625ms

注意事项:

第二篇文章认为您应该始终将更改样式(例如设置width)和进行测量(例如获取offsetWidth)分开以避免布局颠簸。似乎这也是您代码中的问题。

【讨论】:

  • I'm not sure about the reason, but apparently if you store offsetWidth first, it doesn't matter performance-wise whether you use forEach or map。不好了。我不会用枪指着forLoopmap。只是为了时差而寻找那个原因。
  • @kushalvm 我明白了!无论如何,似乎得到offsetWidth 然后立即设置width 似乎是问题所在。如果你分开获取和设置,它会快得多。其原因可能与 DOM 实现内部而不是 JavaScript 代码有关。
  • 是的。我知道这可能是我的问题是关于 - 引擎的内部工作以及由此产生的性能差异 - 以防万一它归结为它
  • 谢谢。我想我会更深入地挖掘。
  • 我添加了一些可能有用的链接。
猜你喜欢
  • 2016-10-08
  • 1970-01-01
  • 2021-02-07
  • 1970-01-01
  • 2023-03-23
  • 2014-10-30
  • 2013-10-22
  • 1970-01-01
  • 2022-01-24
相关资源
最近更新 更多