问题
I have two pieces of code below
Snippet#1
const doubleWidth = (element) => {
const width = element.offsetWidth;
element.style.width = `${width * 2}px`;
};
button.addEventListener('click', (event) => {
boxes.forEach(doubleWidth);
});
Snippet#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 has a lot 48ms compared to snippet #2 which is only 18.4ms. Why is that behaviour?
After all, I am still doing two operation of calculating and settings ( which forcers reflows ) each.
Here's the complete code - https://codepen.io/kushalmahajan/pen/mjXVqp?editors=0010
Update - So , let me explain a bit more
In Snippet #1, I see each time a pattern like calculate, Reset, Calculate, Reset ...so forth
In Snippet #2. That's not the case.
Please base you answers around render pipeline please
回答1:
in Snippet 1 each time you are executing 2 commands:
const width = element.offsetWidth;
element.style.width = `${width * 2}px`;
while in Snippet 2 each time you are executing only 1 command:
element.style.width = `${widths[index] * 2}px`;
Therefore I think it's natural to have more than double the time for the execution. depending on how much time it take to execute the following command:
const width = element.offsetWidth;
For a fair comparison between the 2 snippets I would suggest removing the above command from the loop and check how long it would take to execute snippet 1 in that case.
I hope that helps.
回答2:
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: here's a pen to illustrate this behavior.
You can see that I timed three combinations:
forEachwith immediately gettingoffsetWidthand settingwidthforEachwith storingoffsetWidthand the corresponding element first and then setting width it in a secondforEachmapwith storingoffsetWidthand then settingwidthon the elements in aforEachloop
Option 2. an 3. were basically the same performance wise. Judging from that, I would say that the combination of getting offsetWidth and setting width is a performance bottleneck. Can't really tell you much more than that, sorry!
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');
});
};
OUTPUT:
Double Sizes ForEach: 12.341064453125ms
Double Sizes ForEach 2: 0.539794921875ms
Double Sizes Map: 0.590087890625ms
NOTES:
- what forces layout/reflow
- article on layout performance issues and layout thrashing
The second article argues that you should always separate changing style (e.g. setting width) and taking measurements (e.g. getting offsetWidth) to avoid layout thrashing. Seems that's the problem in your code as well.
来源:https://stackoverflow.com/questions/51609846/checking-width-and-setting-width-inside-foreach-loop-performance