우선 DOM 객체 자체도 js 객체이므로 엄밀히 말하면 이 객체의 조작이 느린 것은 아니지만, 이 객체를 조작한 후에는 다음과 같은 일부 브라우저 동작이 트리거됩니다. 레이아웃 및 페인트. 다음은 주로 이러한 브라우저 동작을 소개하고 페이지가 최종적으로 표시되는 방법을 설명합니다. 또한 코드 관점에서 몇 가지 나쁜 사례와 일부 최적화 솔루션도 설명합니다.
브라우저가 페이지를 렌더링하는 방법브라우저에는 많은 모듈이 있으며, 그 중 렌더링 엔진 모듈이 페이지 렌더링을 담당합니다. 여기에는 WebKit 및 Gecko 등이 포함됩니다. 우리는 이 모듈의 내용만 다룰 것입니다. 먼저 이 프로세스를 텍스트로 간략하게 설명하세요. HTML을 구문 분석하고 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';
속성을 읽습니다. 이전 수정으로 인해 이 속성을 정확하게 얻을 수 있도록 현재 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元素的增删改),可以有如下方案:
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
的callback中,统一让写操作在下一次paint之前执行。
// Read var h1 = element1.clientHeight; // Write requestAnimationFrame(function() { element1.style.height = (h1 * 2) + 'px'; }); // Read var h2 = element2.clientHeight; // Write requestAnimationFrame(function() { element2.style.height = (h2 * 2) + 'px'; });
可以很清楚的观察到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查询时,querySelector
和querySelectorAll
应该是最后的选择,它们功能最强大,但执行效率很差,如果可以的话,尽量用其他方法替代。
下面两个jsperf的链接,可以对比下性能。
1)http://www.php.cn/
2)http://www.php.cn/
上面的内容理论方面的东西偏多,从实践的角度来看,上面讨论的内容,正好是View层需要处理的事情。已经有一个库FastDOM来做这个事情,不过它的代码是这样的:
fastdom.read(function() { console.log('read'); }); fastdom.write(function() { console.log('write'); });
问题很明显,会导致callback hell
,并且也可以预见到像FastDOM这样的imperative的代码缺乏扩展性,关键在于用了requestAnimationFrame
后就变成了异步编程的问题了。要让读写状态同步,那必然需要在DOM的基础上写个Wrapper来内部控制异步读写,不过都到了这份上,感觉可以考虑直接上React了……
总之,尽量注意避免上面说到的问题,但如果用库,比如jQuery的话,layout的问题出在库本身的抽象上。像React引入自己的组件模型,用过virtual DOM来减少DOM操作,并可以在每次state改变时仅有一次layout,我不知道内部有没有用requestAnimationFrame
之类的,感觉要做好一个View层就挺有难度的,之后准备学学React的代码。希望自己一两年后会过来再看这个问题的时候,可以有些新的见解。
위 내용은 JavaScript에서 DOM 작업이 느린 이유에 대한 자세한 소개의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!