循环导致强制回流,而相同的代码不会导致没有循环的回流

问题描述

当我们使用循环时,我发现了一个奇怪的强制回流案例。 Example here

在第一种情况下,我遍历了 200 个元素并使用 for 循环更改了它的类。

function resize1(){
    let children = parent1.children;
        clicked2 = !clicked2;
    for(let i=0;i<200;i++){
        let child = children[i];
        let { width } = window.getComputedStyle(child);
        isLarge = clicked2 ? i % 2 === 0 : i % 2=== 1;
        if(isLarge){
            child.classList.add('large');
            child.classList.remove('small');
        }
        else{
            child.classList.add('small');
            child.classList.remove('large');
        }
        // size = window.getComputedStyle(child).margin;
    }
}

我再次遍历每个子元素并更改相同的类,而不是循环。

let width,isLarge,i =0,child;
  
        child = parent.children[i];
        width = window.getComputedStyle(child);
        isLarge = clicked ? i % 2 === 0 : i % 2=== 1;
        if(isLarge){
            child.classList.add('large');
            child.classList.remove('small');
        }
        else{
            child.classList.add('small');
            child.classList.remove('large');
        }
        i++;
    
        child = parent.children[i];
        width = window.getComputedStyle(child);
        isLarge = clicked ? i % 2 === 0 : i % 2=== 1;
        if(isLarge){
            child.classList.add('large');
            child.classList.remove('small');
        }
        else{
            child.classList.add('small');
            child.classList.remove('large');
        }
        i++;

(*200 个元素) 我知道这种情况很奇怪,但是当我检查性能时,第二种情况在 chrome 开发工具中花费的时间更少,而且回流也只发生在循环情况下。

Performance profiling

解决方法

答案与循环与不循环完全无关,而与您使用 window.getComputedStyle() 的方式有关。

This handy gist 概述了哪些 JavaScript 属性和方法将强制布局/重排。

window.getComputedStyle() 将在以下三个条件之一强制布局:

  1. 元素在影子树中

  2. 有媒体查询(与视口相关的查询)。具体而言,以下情况之一:

  3. 请求的属性是以下之一:

  • height,width

现在,在代码的循环版本中,您有:

let { width } = window.getComputedStyle(child);

但是在您的代码的循环版本中,您有:

width = window.getComputedStyle(child);

有什么区别? width = window.getComputedStyle(child) 创建对计算样式对象的引用,但它访问 width 属性。您(可能是错误地)创建了一个变量 width,它请求宽度属性,而是请求计算的样式对象本身,这本身并不足以强制布局。

但是,let { width } = window.getComputedStyle(child) 添加了将 width 属性解构到变量声明的额外步骤,从而有效地访问该属性并为您的 {{} 的每次 迭代强制布局1}} 循环。

您可以在性能时间线 (Safari) 中看到 for 导致的额外强制布局:

enter image description here

如果您修改代码的非循环版本以访问所有 200 个案例的 let { width } = window.getComputedStyle(child)width 属性,您将在非循环部分获得相同的强制布局:

enter image description here

或者,您可以简单地删除 getComputedStyle() 的所有用途,因为在您的代码的当前版本中它没有用于任何事情,并完全消除强制布局。

enter image description here