JavaScript에서 DOM 작업이 느린 이유에 대한 자세한 소개

黄舟
풀어 주다: 2017-03-08 14:59:38
원래의
1214명이 탐색했습니다.

우선 DOM 객체 자체도 js 객체이므로 엄밀히 말하면 이 객체의 조작이 느린 것은 아니지만, 이 객체를 조작한 후에는 다음과 같은 일부 브라우저 동작이 트리거됩니다. 레이아웃 및 페인트. 다음은 주로 이러한 브라우저 동작을 소개하고 페이지가 최종적으로 표시되는 방법을 설명합니다. 또한 코드 관점에서 몇 가지 나쁜 사례와 일부 최적화 솔루션도 설명합니다.

브라우저가 페이지를 렌더링하는 방법

브라우저에는 많은 모듈이 있으며, 그 중 렌더링 엔진 모듈이 페이지 렌더링을 담당합니다. 여기에는 WebKit 및 Gecko 등이 포함됩니다. 우리는 이 모듈의 내용만 다룰 것입니다.

먼저 이 프로세스를 텍스트로 간략하게 설명하세요.

HTML을 구문 분석하고 DOM 트리를 생성
  • 각 스타일을 구문 분석하고 결합합니다. DOM 트리를 사용하여 렌더 트리를 생성
  • 상자의 위치 및 크기와 같은 렌더 트리의 각 노드에 대한 레이아웃 정보를 계산
  • 렌더 트리를 기반으로 그리기 및 브라우저의 UI 레이어 사용
  • DOM 트리의 노드와 렌더 트리의 노드는 일대일 대응이 아닙니다. "
  • " 노드는 DOM 트리에만 존재하며 렌더 트리에는 나타나지 않습니다.

display:none"

위의 노드입니다. 그림은 Webkit의 기본 프로세스이며 Gecko 측면에서는 다를 수 있습니다. Gecko의 흐름도는 다음과 같습니다. 그러나 기사의 다음 내용에서는 Webkit 용어를 일률적으로 사용합니다. 🎜>링크의 위치가 첫 번째 화면 렌더링에 영향을 미치는 등 페이지 렌더링에 영향을 미치는 요소는 많습니다. 하지만 여기서는 주로 레이아웃 관련 콘텐츠에 중점을 둡니다.

페인트는 시간이 많이 걸립니다. 그러나 레이아웃은 더 시간이 많이 걸리는 프로세스입니다. 레이아웃은 하향식 또는 상향식으로 수행되며 하나의 레이아웃이라도 전체 문서 레이아웃을 다시 계산해야 합니다.

그러나 레이아웃은 당연히 피할 수 없는 일이므로 주로 레이아웃 횟수를 최소화하려고 합니다.

어떤 상황에서 브라우저가 레이아웃을 수행할까요

레이아웃 횟수를 최소화하는 방법을 고려하기 전에 먼저 이해해야 합니다. 브라우저가 레이아웃(리플로우)을 수행하는 경우를 일반적으로 레이아웃이라고 합니다. 이 작업은 문서에서 요소의 위치와 크기를 계산하는 데 사용됩니다. HTML을 처음 로드할 때 중요한 단계입니다. 레이아웃 외에 js 스크립트를 실행하면 브라우저에서도 레이아웃이 실행됩니다.

일반적으로 브라우저의 레이아웃은 게으르다. 즉, js 스크립트가 실행될 때 DOM은 업데이트되지 않습니다. DOM에 대한 모든 수정 사항은 일시적으로 대기열에 저장됩니다. 현재 js 실행 컨텍스트가 실행을 완료한 후 이 수정 사항을 기반으로 레이아웃이 수행됩니다.

그러나 js 코드에서 최신 DOM 노드 정보를 즉시 얻으려면 브라우저에서 미리 레이아웃을 실행해야 합니다. 이것이 DOM 성능 문제의 주요 원인입니다. >다음 작업은 규칙을 위반하고 브라우저가 레이아웃을 실행하도록 합니다.

js를 통해 계산해야 하는 DOM 속성을 가져옵니다

DOM 요소 추가 또는 삭제

브라우저 창 크기 조정

  • 글꼴 변경

  • 활성화 hover

  • js를 통해 DOM 요소 스타일을 수정하고 스타일에 크기 변경이 포함됩니다

  • 직관적으로 느껴보자 예를 들어

  • // Read
    var h1 = element1.clientHeight;
    
    // Write (invalidates layout)
    element1.style.height = (h1 * 2) + 'px';
    
    // Read (triggers layout)
    var h2 = element2.clientHeight;
    
    // Write (invalidates layout)
    element2.style.height = (h2 * 2) + 'px';
    
    // Read (triggers layout)
    var h3 = element3.clientHeight;
    
    // Write (invalidates layout)
    element3.style.height = (h3 * 2) + 'px';
    로그인 후 복사

  • clientHeight, 이 속성을 계산해야 하므로 브라우저 레이아웃이 실행됩니다. Chrome(v47.0)의 개발자 도구를 사용하여 살펴보겠습니다(스크린샷의 타임라인 기록은 필터링되었으며 레이아웃만 표시됨).
In the 위의 예에서 코드는 먼저 한 요소의 스타일을 수정한 다음 다른 요소의

속성을 ​​읽습니다. 이전 수정으로 인해 이 속성을 정확하게 얻을 수 있도록 현재 DOM이 더티로 표시됩니다. , 브라우저는 레이아웃을 수행합니다(Chrome의 개발자 도구는 이러한 성능 문제를 양심적으로 상기시켜주었습니다).

이 코드를 최적화하는 방법은 매우 간단합니다. 필수 속성을 미리 읽고 함께 수정하면 됩니다.

// Read
var h1 = element1.clientHeight;  
var h2 = element2.clientHeight;  
var h3 = element3.clientHeight;

// Write (invalidates layout)
element1.style.height = (h1 * 2) + 'px';  
element2.style.height = (h2 * 2) + 'px';  
element3.style.height = (h3 * 2) + 'px';
로그인 후 복사

이번 상황을 살펴보세요.

clientHeight

여기에 다른 최적화 솔루션이 있습니다.

레이아웃을 최소화하는 솔루션

위에서 언급한 일괄 읽기 및 쓰기는 주로 계산해야 하는 속성 값을 얻음으로써 발생합니다. 그러면 어떤 값을 계산해야 할까요?

这个链接里有介绍大部分需要计算的属性:http://www.php.cn/

再来看看别的情况:

面对一系列DOM操作

针对一系列DOM操作(DOM元素的增删改),可以有如下方案:

  • documentFragment

  • display: none

  • cloneNode

比如(仅以documentFragment为例):

var fragment = document.createDocumentFragment();  
for (var i=0; i < items.length; i++){  
  var item = document.createElement("li");
  item.appendChild(document.createTextNode("Option " + i);
  fragment.appendChild(item);
}
list.appendChild(fragment);
로그인 후 복사

这类优化方案的核心思想都是相同的,就是先对一个不在Render tree上的节点进行一系列操作,再把这个节点添加回Render tree,这样无论多么复杂的DOM操作,最终都只会触发一次layout。

面对样式的修改

针对样式的改变,我们首先需要知道并不是所有样式的修改都会触发layout,因为我们知道layout的工作是计算RenderObject的尺寸和大小信息,那么我如果只是改变一个颜色,是不会触发layout的。

这里有一个网站CSS triggers,详细列出了各个CSS属性对浏览器执行layout和paint的影响。

像下面这种情况,和上面讲优化的部分是一样的,注意下读写即可。

elem.style.height = "100px"; // mark invalidated  
elem.style.width = "100px";  
elem.style.marginRight = "10px";

elem.clientHeight // force layout here
로그인 후 복사

但是要提一下动画,这边讲的是js动画,比如:

function animate (from, to) {  
  if (from === to) return

  requestAnimationFrame(function () {
    from += 5
    element1.style.height = from + "px"
    animate(from, to)
  })
}

animate(100, 500)
로그인 후 복사

动画的每一帧都会导致layout,这是无法避免的,但是为了减少动画带来的layout的性能损失,可以将动画元素绝对定位,这样动画元素脱离文本流,layout的计算量会减少很多。

使用requestAnimationFrame

任何可能导致重绘的操作都应该放入requestAnimationFrame

在现实项目中,代码按模块划分,很难像上例那样组织批量读写。那么这时可以把写操作放在requestAnimationFrame的callback中,统一让写操作在下一次paint之前执行。

// Read
var h1 = element1.clientHeight;

// Write
requestAnimationFrame(function() {  
  element1.style.height = (h1 * 2) + &#39;px&#39;;
});

// Read
var h2 = element2.clientHeight;

// Write
requestAnimationFrame(function() {  
  element2.style.height = (h2 * 2) + &#39;px&#39;;
});
로그인 후 복사

可以很清楚的观察到Animation Frame触发的时机,MDN上说是在paint之前触发,不过我估计是在js脚本交出控制权给浏览器进行DOM的invalidated check之前执行。

其他注意点

除了由于触发了layout而导致性能问题外,这边再列出一些其他细节:

缓存选择器的结果,减少DOM查询。这里要特别提下HTMLCollection。HTMLCollection是通过document.getElementByTagName得到的对象类型,和数组类型很类似但是每次获取这个对象的一个属性,都相当于进行一次DOM查询:

var ps = document.getElementsByTagName("p");  
for (var i = 0; i < ps.length; i++){  //infinite loop  
  document.body.appendChild(document.createElement("p"));
}
로그인 후 복사

比如上面的这段代码会导致无限循环,所以处理HTMLCollection对象的时候要做些缓存。

另外,减少DOM元素的嵌套深度并优化css,去除无用的样式对减少layout的计算量有一定帮助。

在DOM查询时,querySelectorquerySelectorAll应该是最后的选择,它们功能最强大,但执行效率很差,如果可以的话,尽量用其他方法替代。

下面两个jsperf的链接,可以对比下性能。

1)http://www.php.cn/

2)http://www.php.cn/

自己对View层的想法

上面的内容理论方面的东西偏多,从实践的角度来看,上面讨论的内容,正好是View层需要处理的事情。已经有一个库FastDOM来做这个事情,不过它的代码是这样的:

fastdom.read(function() {  
  console.log(&#39;read&#39;);
});

fastdom.write(function() {  
  console.log(&#39;write&#39;);
});
로그인 후 복사

问题很明显,会导致callback hell,并且也可以预见到像FastDOM这样的imperative的代码缺乏扩展性,关键在于用了requestAnimationFrame后就变成了异步编程的问题了。要让读写状态同步,那必然需要在DOM的基础上写个Wrapper来内部控制异步读写,不过都到了这份上,感觉可以考虑直接上React了……

总之,尽量注意避免上面说到的问题,但如果用库,比如jQuery的话,layout的问题出在库本身的抽象上。像React引入自己的组件模型,用过virtual DOM来减少DOM操作,并可以在每次state改变时仅有一次layout,我不知道内部有没有用requestAnimationFrame之类的,感觉要做好一个View层就挺有难度的,之后准备学学React的代码。希望自己一两年后会过来再看这个问题的时候,可以有些新的见解。


위 내용은 JavaScript에서 DOM 작업이 느린 이유에 대한 자세한 소개의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿