JavaScript는 코드 분석을 효율적으로 실행합니다_javascript 팁

WBOY
풀어 주다: 2016-05-16 18:32:00
원래의
1640명이 탐색했습니다.

作者 Mark 'Tarquin' Wilton-Jones · 2006年11月2日

本文翻译自 Efficient JavaScript

原译文地址 http://kb.operachina.com/node/207

传统上,网页中不会有大量的脚本,至少脚本很少会影响网页的性能。但随着网页越来越像 Web 应用程序,脚本的效率对网页性能影响越来越大。而且使用 Web 技术开发的应用程序现在越来越多,因此提高脚本的性能变得很重要。

对于桌面应用程序,通常使用编译器将源代码转换为二进制程序。编译器可以花费大量时间优化最终二进制程序的效率。Web 应用程序则不同。因为Web应用程序需要运行在不同的浏览器、平台和架构中,不可能事先完全编译。浏览器在获得脚本后要执行解释和编译工作。用户要求不仅要求网页能快速的载入,而且要求最终 Web 应用程序执行的效果要和桌面应用程序的一样流畅。Web 应用程序应能运行在多种设备上,从普通的桌面电脑到手机。

浏览器并不很擅长此项工作。虽然 Opera 有着当前最快的脚本引擎,但浏览器有不可避免的局限性,这时就需要 Web 开发者的帮助。Web开发者提高 Web 应用程序的性能的方法很多而且也很简单,如只需要将一种循环变成另一种、将组合样式分解成三个或者只添加实际需要的脚本。

本文从 ECMAScript/JavaScript, DOM, 和页面载入方面分别介绍几种简单的能提高 Web 应用程序性能的方法。

目录

ECMAScript

  1. 避免使用 evalFunction 构造函数
    1. 重写 eval
    2. 如果你需要函数,那就用函数
  2. 避免使用 with
  3. 不要在影响性能的关键函数中使用 try-catch-finally
  4. 分隔 evalwith
  5. 避免使用全局变量
  6. 注意隐式对象转换
  7. 在关键函数中避免 for-in
  8. 优化 string 合并
  9. 基本运算符比函数调用更快
  10. setTimeout()setInterval()传送函数名,而不要传送字符串

DOM

  1. 重绘和 reflow
    1. 减少 reflow 次数
    2. 最小化 reflow 影响
  2. 修改 DOM 树
  3. 修改不可见元素
  4. 测量大小
  5. 一次修改多个样式值
  6. 用流畅性换取速度
  7. 避免搜索大量节点
  8. 使用 XPath 提高速度
  9. 避免在遍历 DOM 时修改 DOM
  10. 使用变量保存 DOM 值

页面载入

  1. 避免保存来自其他文档的引用
  2. 快速历史浏览
  3. 使用 XMLHttpRequest
  4. 动态创建 SCRIPT 元素
  5. location.replace() 控制历史项

ECMAScript

避免使用 evalFunction 构造函数

每次 eval Function 构造函数作用于字符串表示的源代码时,脚本引擎都需要将源代码转换成可执行代码。这是很消耗资源的操作 —— 通常比简单的函数调用慢100倍以上。

eval 函数效率特别低,由于事先无法知晓传给 eval 的字符串中的内容,eval在其上下文中解释要处理的代码,也就是说编译器无法优化上下文,因此只能有浏览器在运行时解释代码。这对性能影响很大。

Function 构造函数比 eval 略好,因为使用此代码不会影响周围代码;但其速度仍很慢。

다시 작성 eval

eval 비효율적일 뿐만 아니라 대부분의 경우 사용이 전혀 필요하지 않습니다. 많은 경우 eval을 사용하는 이유는 정보가 문자열 형식으로 제공되기 때문이며, 개발자는 eval만이 이 정보를 사용할 수 있다고 잘못 생각하고 있습니다. 다음 예는 일반적인 오류입니다.

코드 복사 코드는 다음과 같습니다.
function getProperty(oString) ) { var oReference; eval('oReference = test.prop.' oString) return oReference };
아래 코드는

을 사용하지 않고 완전히 동일한 기능을 수행합니다. eval

코드 복사 코드는 다음과 같습니다. 다음:
function getProperty(oString) { return test.prop[oString] }
후자는 Opera 9, Firefox, Internet Explorer에서 전자보다 95% 더 빠르고 Safari에서는 85% 더 빠릅니다. (이 비교에는 함수 호출 시간이 포함되지 않습니다.)

함수가 필요하다면 함수를 사용하세요

일반적인

생성자 사용법은 다음과 같습니다. Function

코드 복사 코드는 다음과 같습니다.
function addMethod(oObject,oProperty,oFunctionCode) { oObject[oProperty] = new Function(oFunctionCode); } addMethod(myObject,'rotateBy90','this.angle=(this.angle 90)60'); ,'rotateBy60','this.angle=(this.angle 60)60')
아래 코드는

생성자를 사용하지 않지만 동일한 기능을 제공합니다. 익명 함수를 생성하여: Function

코드 복사 코드는 다음과 같습니다.
function addMethod(oObject,oProperty,oFunction) { oObject[oProperty] = oFunction; } addMethod(myObject,'rotateBy90',function () { this.angle=( this.angle 90 )60; }); addMethod(myObject,'rotateBy60',function () { this.angle=(this.angle 60)60; });
사용하지 마세요

with편리해 보이지만

매우 비효율적입니다.

구조는 변수를 사용할 때 스크립트 엔진이 검색할 또 다른 범위를 만듭니다. 이는 그 자체로는 성능에 약간의 영향을 미칩니다. 그러나 심각한 점은 이 범위의 내용이 컴파일 타임에 알려지지 않기 때문에 컴파일러가 다른 범위(예: 함수에 의해 생성된 범위)에 대해 할 수 있는 것처럼 이를 최적화할 수 없다는 것입니다. with with또 다른 효율적이고 편리한 방법은 변수를 사용하여 개체를 참조한 다음 변수를 사용하여 개체 속성에 액세스하는 것입니다. 그러나 이는 속성이 문자열이나 부울과 같은 리터럴 유형이 아닌 경우에만 적용됩니다.

다음 코드를 고려하세요.

코드 복사 코드는 다음과 같습니다. with( test .information.settings .files ) { 기본 = '이름'; 보조 = '역할' }
다음 코드가 더 효율적입니다.

<pre class="brush:javascript">var testObject = test.information.settings.files; 
testObject.primary = 'names'; 
testObject.secondary = 'roles'; 
testObject.tertiary = 'references'; 
로그인 후 복사
성능에 영향을 미치는 주요 기능에는

을 사용하지 마세요. try-catch-finally

구조가 좀 특이하네요. 다른 구문 구성과 달리 런타임의 현재 범위에 새 변수를 만듭니다.

이 실행될 때마다 캡처된 예외 개체가 변수에 할당됩니다. 이 변수는 어떤 스크립트에도 속하지 않습니다. try-catch-finally 문의 시작 부분에 생성되고 끝 부분에서 삭제됩니다. catch catch이 기능은 특수하고 런타임 시 동적으로 생성되고 소멸되기 때문에 일부 브라우저에서는 이를 효율적으로 처리하지 못합니다. 중요한 루프에 catch 문을 배치하면 성능에 큰 영향을 미칩니다.

가능하다면 자주 호출되지 않는 스크립트에서 예외를 처리하거나 작업이 지원되는지 확인하여 예외를 방지하세요. 다음 예에서 필수 속성이 없으면 루프 문에서 많은 예외가 발생합니다.

코드 복사 코드 다음과 같습니다:
var oProperties = ['first','second','third',...,'nth'], i; for( i = 0; i < oProperties.length; i++ ) { try { test[oProperties[i]].someproperty = somevalue; } catch(e) { ... } }
로그인 후 복사
로그인 후 복사
很多情况下,可把 try-catch-finally 结构移到循环外部。这样做稍微改变了程序语义,因为如果抛出异常,将停止整个循环:
로그인 후 복사
로그인 후 복사
var oProperties = ['first','second','third',...,'nth'], i; 
try { 
for( i = 0; i < oProperties.length; i++ ) { 
test[oProperties[i]].someproperty = somevalue; 
} 
} catch(e) { 
... 
} 
로그인 후 복사

有时可用属性检测或其他检测代替 try-catch-finally 结构:

[code]var oProperties = ['first','second','third',...,'nth'], i; for( i = 0; i < oProperties.length; i++ ) { if( test[oProperties[i]] ) { test[oProperties[i]].someproperty = somevalue; } }

分隔 evalwith

因为 eval 和 with 结构严重影响性能,应该尽量避免使用这些结构。但如不得不使用时, 避免在频繁被调用的函数中或循环中使用这些结构。最好将这些结构放在只运行一次,或少量几次的代码中,并不要将其放在对性能要求较高的代码中。

如果可能,尽量将这些结构和其他代码分隔开,这样他们就不会影响脚本性能。如将其放在顶级函数中,或只执行一次然后保存运行结果,避免再次使用。

try-catch-finally 结构在一些浏览器中也会影响性能,包括 Opera ,因此最好也将其分隔。

避免使用全局变量

全局变量使用简单,因此很容易禁不住诱惑在脚本中使用全局变量。但有时全局变量也会影响脚本性能。

首先,如果函数或其他作用域内引用了全局变量,则脚本引擎不得不一级一级查看作用域直到搜索到全局作用域。查询本地作用域变量更快。

其次,全局变量将始终存在在脚本生命周期中。而本地变量在本地作用域结束后就将被销毁,其所使用的内存也会被垃圾收集器回收。

最后,window 对象也共享全局作用域,也就是说本质上是两个作用域而不是一个。使用全局变量不能像使用本地变量那样使用前缀,因此脚本引擎要花更多时间查找全局变量。

也可在全局作用域中创建全局函数。函数中可以调用其他函数,随着函数调用级数增加,脚本引擎需要花更多时间才能找到全局变量以找到全局变量。

考虑下面的简单例子,is 是全局作用域且函数使用这两个全局变量:

复制代码 代码如下:
var i, s = ''; function testfunction() { for( i = 0; i < 20; i++ ) { s += i; } } testfunction();

下面的函数效率更高。在大多数浏览器中,包括 Opera 9、最新版 Internet Explorer, Firefox, Konqueror 和 Safari,后者执行速度比上面代码快30%。

function testfunction() { 
var i, s = ''; 
for( i = 0; i < 20; i++ ) { 
s += i; 
} 
} 
testfunction(); 
로그인 후 복사

注意隐式对象转换

Literal,如字符串、数字和布尔值在 ECMAScript 中有两种表示方法。 每个类型都可以创建变量值或对象。如 var oString = 'some content';, 创建了字符串值,而 var oString = new String('some content');创建了字符串对象。

所有的属性和方法都定义在 string 对象中,而不是 string 值中。每次使用 string 值的方法或属性,ECMAScript 引擎都会隐式的用相同 string 值创建新的 string 对象。此对象只用于此请求,以后每次视图调用 string值方法是都会重新创建。

下面的代码将要求脚本引擎创建21个新 string 对象,每次使用 length 属性时都会产生一个,每一个 charAt 方法也会产生一个:

var s = '0123456789'; 
for( var i = 0; i < s.length; i++ ) { 
s.charAt(i); 
} 
로그인 후 복사

下面的代码和上面相同,但只创建了一个对象,因此其效率更高:

复制代码 代码如下:
var s = new String('0123456789'); for( var i = 0; i < s.length; i++ ) { s.charAt(i); }

코드가 리터럴 값에 대한 메서드를 자주 호출하는 경우 위의 예와 같은 객체 생성을 고려해야 합니다.

이 기사에 있는 대부분의 팁은 모든 브라우저에서 작동하지만 이 팁은 Opera에만 적용됩니다. 이 최적화 팁은 Opera에서만큼 Internet Explorer 및 Firefox에서 개선되지 않습니다.

중요한 기능에서는 for-in

를 피하세요.

for-in은 특히 단순한 for 루프가 더 적절한 경우 오용되는 경우가 많습니다. for-in 루프에서는 스크립트 엔진이 열거 가능한 모든 속성 목록을 생성한 다음 중복 항목을 확인해야 합니다.

때때로 열거 가능한 속성이 스크립트에 알려져 있습니다. 이 시점에서 간단한 for 루프는 특히 배열과 같이 순차적 숫자 열거를 사용할 때 모든 속성을 반복할 수 있습니다.

다음 내용이 올바르지 않습니다for-in 루프 사용법:

코드 복사 코드는 다음과 같습니다.
var oSum = 0; for( var i in oArray ) { oSum = oArray[i] }

for 루프는 의심할 여지 없이 더 효율적입니다.

코드 복사 코드는 다음과 같습니다.
var oSum = 0; var oLength = oArray.length; for( var i = 0; i < oLength; i ) { oSum = oArray[i] }

문자열 병합 최적화

문자열 병합은 상대적으로 느립니다. 연산자는 결과를 변수에 저장하지 않습니다. 새 문자열 개체를 만들고 결과를 이 개체에 할당합니다. 아마도 새 개체가 변수에 할당될 수 있습니다. 다음은 일반적인 문자열 병합 문입니다.

코드 복사 코드는 다음과 같습니다.
a = ' x' 'y'

이 코드는 먼저 병합된 'xy' 값을 저장하기 위해 임시 문자열 개체를 만든 다음 이를 a 변수와 병합하고 마지막으로 결과를 a에 할당합니다. 아래 코드는 두 개의 개별 명령을 사용하지만 매번 a에 직접 할당하므로 임시 문자열 개체를 만들 필요가 없습니다. 결과적으로 대부분의 브라우저에서 후자가 전자보다 20% 더 빠르고 메모리를 덜 소모합니다.

코드 복사 Code As 다음:
a = 'x'; a = 'y';
기본 연산자가 함수 호출보다 빠릅니다

단독으로 사용하면 효과가 뚜렷하지 않지만, 중요한 루프나 고성능이 필요한 함수에서 함수 호출 대신 기본 연산자를 사용하면 스크립트 성능이 향상될 수 있습니다. 예를 들어 배열의

push 메서드가 있는데, 이는 배열의 끝에 직접 할당하는 것보다 효율성이 떨어집니다. 또 다른 예는 Math 객체 방법입니다. 대부분의 경우 간단한 수학 연산자가 더 효율적이고 적합합니다.

코드 복사 코드는 다음과 같습니다.
var min = Math.min(a,b); A.푸시(v)
다음 코드는 동일한 기능을 구현하지만 더 효율적입니다.

코드 복사 코드는 다음과 같습니다.
var min = a < b ? a : b A[A.length] = v;
문자열 대신 함수 이름을

setTimeout()에 전달합니다. setInterval()

메소드는 setTimeout()과 유사합니다. 전달된 매개변수가 문자열이면 일정 시간이 지나면 setInterval()처럼 문자열 값이 실행됩니다. 물론 비효율성은 eval과 같습니다. eval eval 그러나 이러한 메소드는 함수를 첫 번째 매개변수로 받아들일 수도 있습니다. 이 함수는 일정 시간 후에 호출되지만 이 함수는 컴파일 타임에 해석되고 최적화될 수 있으므로 성능이 향상됩니다. 문자열을 매개변수로 사용하는 대표적인 예는 다음과 같습니다.

코드 복사 코드는 다음과 같습니다.setInterval('updateResults() ',1000); setTimeout('x =3;prepareResult();if(!hasCancelled){runmore();}',500);

第一个语句可以直接传递函数名。第二个语句中,可以使用匿名函数封装代码:

setInterval(updateResults,1000); 
setTimeout(function () { 
x += 3; 
prepareResult(); 
if( !hasCancelled ) { 
runmore(); 
} 
},500); 
로그인 후 복사

需要注意的是 timeout或时间延迟可能并不准确。通常浏览器会花比要求更多的时间。有些浏览器会稍微提早完成下一个延迟以补偿。有些浏览器每次可能都会等待准确时间。很多因素,如 CPU 速度、线程状态和 JavaScript负载都会影响时间延迟的精度。大多数浏览器无法提供1ms以下的延迟,可能会设置最小可能延迟,通常在10 和 100 ms之间。

DOM

通常主要有三种情况引起 DOM 运行速度变慢。第一就是执行大量 DOM 操作的脚本,如从获取的数据中建造新的 DOM 树。第二种情况是脚本引起太多的 reflow 或重绘。第三种情况是使用较慢的 DOM 节点定位方法。

第二种和第三种情况比较常见且对性能影响比较严重,因此先介绍前两种情况。

重绘(Repaint)和 reflow

重绘也被称为重画,每当以前不可见的元素变得可见(或反之)时就需要重绘操作;重绘不会改变页面布局。如给元素添加轮廓、改变背景颜色、改变样式。重绘对性能影响很大,因为需要脚本引擎搜索所有元素以确定哪些是可见的及哪些是应被显示的。

Reflow 是更大规模的变化。当 DOM 数被改变时、影响布局的样式被修改时、当元素的 className属性被修改时或当浏览器窗口大小变化时都会引起 reflow。脚本引擎必须 reflow 相关元素以确定哪些部分不应被现实。其子节点也会被reflow 以考虑其父节点的新布局。DOM 中此元素之后出现的元素也被 reflow以计算新布局,因为它们的位置可能已被移动了。祖先节点也需要 reflow 以适应子节点大小的改变。总之,所有元素都需被重绘。

Reflow 从性能角度来说是非常耗时的操作,是导致 DOM 脚本较慢的主要原因之一,特别在手机等处理能力较弱的设备上。很多情况下,reflow 和重新布局整个网页耗时相近。

减少 reflow 次数

很多情况下脚本需要进行会引起 reflow 或重绘的操作,如动画就需要 reflow 操作,因此 reflow 是 Web 开发不可或缺的特性。为了让脚本能快速运行,应在不影响整体视觉效果的情况下尽量减少 reflow 次数。

浏览器可以选择缓存 reflow 操作,如可以等到脚本线程结束后才 reflow 以呈现变化。Opera 可以等待足够数量的改变后才reflow、或等待足够长时间后才 reflow、或等待脚本线程结束后才reflow。也就是说如果一个脚本线程中的发生很多间隔很小的改变时,可能只引起一个 reflow 。但开发者不能依赖此特性,特别是考虑到运行Opera 的不同设备的运算速度有很大差异。

注意不同元素的 reflow 消耗时间不同。Reflow 表格元素消耗的时间最多是 Reflow 块元素时间的3倍。

最小化 reflow 影响

正常的 reflow 可能影响整个页面。reflow 的页面内容越多,则 reflow 操作的时间也越长。Reflow的页面内容越多,需要的时间也就越长。位置固定的元素不影响页面的布局,因此如果它们 reflow 则只需 reflow其本身。其背后的网页需要被重绘,但这比 reflow 整个页面要快得多。

所以动画不应该被用于整个页面,最好用于固定位置元素。大部分动画符合此要求。

修改 DOM 树

修改 DOM 树导致 reflow 。向 DOM 中添加新元素、修改 text 节点值或修改属性都可能导致 reflow。顺序执行多个修改会引起超过一个 reflow,因此最好将多个修改放在不可见的 DOM 树 fragment 中。这样就只需要一次 DOM 修改操作:

复制代码 代码如下:
var docFragm = document.createDocumentFragment(); var elem, contents; for( var i = 0; i < textlist.length; i++ ) { elem = document.createElement('p'); contents = document.createTextNode(textlist[i]); elem.appendChild(contents); docFragm.appendChild(elem); } document.body.appendChild(docFragm);

也可以在元素的克隆版本中进行多个 DOM 树修改操作,在修改结束后用克隆版本替换原版本即可,这样只需要一个 reflow操作。注意如果元素中包含表单控件,则不能使用此技巧,因为用户所做修改将无法反映在 DOM树种。此技巧也不应该用于绑定事件处理器的元素,因为理论上不应该克隆这些元素。

复制代码 代码如下:
var original = document.getElementById('container'); var cloned = original.cloneNode(true); cloned.setAttribute('width','50%'); var elem, contents; for( var i = 0; i < textlist.length; i++ ) { elem = document.createElement('p'); contents = document.createTextNode(textlist[i]); elem.appendChild(contents); cloned.appendChild(elem); } original.parentNode.replaceChild(cloned,original);

修改不可见元素

如果一个元素的 display 样式被设置为 none,即使其内容变化也不再需要重绘此元素,因为根本就不会显示此元素。可以利用这一点。如果需要对一个元素或其内容做出多个修改,又无法将这些更改放在一个重绘中,则可以先将元素设置为 display:none ,做出修改后,在把元素改回原来状态。

上面方法将导致两个额外的 reflow,一个是隐藏元素时另一个是重新显示此元素时,但此方法的总体效率仍较高。如果隐藏的元素影响滚动条位置,上面的方法也有可能会引起滚动条跳动。但此技术也被用于固定位置元素而不会引起任何不好看的影响。

复制代码 代码如下:
var posElem = document.getElementById('animation'); posElem.style.display = 'none'; posElem.appendChild(newNodes); posElem.style.width = '10em'; ... other changes ... posElem.style.display = 'block';

测量大小

如上面所述,浏览器可能会缓存多个修改一起执行,并只执行一次 reflow 。但注意为保证结果正确,测量元素大小也会引起 reflow 。尽管这不会造成任何重绘,但仍会在后台进行 reflow 操作。

使用 offsetWidth 这样的属性或 getComputedStyle 这样的方法都会引起 reflow 。即使不使用返回的结果,上述操作也会引起立即 reflow。如果重复需要测量结果,可以考虑只测量一次但用变量保存结果。

复制代码 代码如下:
var posElem = document.getElementById('animation'); var calcWidth = posElem.offsetWidth; posElem.style.fontSize = ( calcWidth / 10 ) + 'px'; posElem.firstChild.style.marginLeft = ( calcWidth / 20 ) + 'px'; posElem.style.left = ( ( -1 * calcWidth ) / 2 ) + 'px'; ... other changes ...

一次修改多个样式值

与 DOM 树修改相似,可将多个样式修改一次进行,以尽量减少重绘或 reflow数目。常见设置样式方法是逐个设置:

复制代码 代码如下:
var toChange = document.getElementById('mainelement'); toChange.style.background = '#333'; toChange.style.color = '#fff'; toChange.style.border = '1px solid #00f';

上面代码可能引起多次 reflow 和重绘。有两种改进方法。如果元素采用了多个样式,而且这些样式值事先知道,可以通过修改元素 class 使用新样式:

div { 
background: #ddd; 
color: #000; 
border: 1px solid #000; 
} 
div.highlight { 
background: #333; 
color: #fff; 
border: 1px solid #00f; 
} 
... 
document.getElementById('mainelement').className = 'highlight'; 
로그인 후 복사

第二种方法是为元素定义新样式,而不是一个个赋值。这主要用于动态修改,如在动画中,无法事前知道新样式值。通过使用 style 对象的 cssText 属性,或者通过 setAttribute. 可以实现此技巧。Internet Explorer 不允许第二种形式,支持第一种形式。有些较老的浏览器,包括 Opera 8 需要使用第二种形式,不支持第一种形式。最简单的方式是测试看是否支持第一种形式,如果支持就使用,如果不支持则使用第二种形式。

复制代码 代码如下:
var posElem = document.getElementById('animation'); var newStyle = '배경: ' newBack ';' 'border: ' newBorder ';'; .cssText ) != '정의되지 않음' ) { posElem.style.cssText = newStyle } else { posElem.setAttribute('style',newStyle) }

속도를 유창하게 교환

개발자라면 일반적으로 더 작은 시간 간격이나 더 작은 변경을 사용하여 애니메이션이 최대한 원활하게 실행되기를 원할 것입니다. 예를 들어 10ms마다 애니메이션을 업데이트하거나 매번 1픽셀씩 이동합니다. 이 애니메이션은 데스크톱 컴퓨터나 일부 브라우저에서 완벽하게 작동할 수 있습니다. 그러나 10ms 간격은 브라우저가 CPU를 100% 사용하여 달성할 수 있는 최소 간격일 수 있습니다. 일부 브라우저에서는 이를 수행할 수도 없습니다. 초당 100번의 리플로우를 요구하는 것은 대부분의 브라우저에서 쉽지 않습니다. 성능이 낮은 컴퓨터나 기타 장치에서는 이 속도를 달성하지 못할 수 있으며 이러한 장치에서는 애니메이션이 매우 느리거나 응답하지 않을 수도 있습니다.

따라서 당분간 개발자의 자부심을 제쳐두고 속도를 위해 부드러움을 희생하는 것이 가장 좋습니다. 시간 간격을 50ms로 변경하거나 애니메이션 단계를 5픽셀로 설정하면 컴퓨팅 리소스를 덜 소모하며 저성능 장치에서도 정상적으로 실행됩니다.

많은 수의 노드 검색을 피하세요

노드를 찾아야 할 경우 DOM 내장 메소드와 컬렉션을 사용하여 검색 범위를 좁혀보세요. 특정 속성이 포함된 요소를 찾으려면 다음 코드를 사용할 수 있습니다.

코드 복사 코드는 다음과 같습니다. 다음과 같습니다:
var allElements = document.getElementsByTagName('*'); for( var i = 0; i < allElements.length; i ) { if( allElements[i].hasAttribute('someattr' ) ) { .. } }

XPath와 같은 고급 기술에 대해 들어본 적이 없더라도 위 코드에는 속도를 저하시키는 두 가지 문제가 있다는 것을 알 수 있습니다. 먼저 검색 범위를 좁히는 대신 모든 요소를 ​​검색합니다. 둘째, 필요한 요소를 찾은 후에도 검색이 계속됩니다. 찾고 있는 요소가 ID가 inhere인 div에 있는 것으로 알려진 경우 다음 코드를 사용하는 것이 가장 좋습니다.

코드 복사 코드는 다음과 같습니다.
var allElements = document.getElementById('inhere').getElementsByTagName('*') for( var i = 0; i < allElements.length; i ) { if ( allElements[i].hasAttribute('someattr') ) { ... 중단;
찾고 있는 요소가 div의 직계 하위 노드인 것으로 알려진 경우 다음 코드가 더 빠릅니다.

코드 복사 코드는 다음과 같습니다.
var allChildren = document.getElementById('inhere').childNodes; for( var i = 0; i < allChildren.length; i ) { if( allChildren[i].nodeType == 1 && allChildren[i].hasAttribute('someattr') ) { ... break;
기본 아이디어는 DOM 노드를 하나씩 살펴보는 것을 피하는 것입니다. DOM에는
childNodes

컬렉션을 재귀적으로 검색하는 것보다 더 효율적인 DOM 2 Traversal TreeWalker와 같은 더 좋고 빠른 방법이 많이 있습니다. 속도를 위해 XPath 사용

H2-H4 요소를 기반으로 HTML 웹페이지에 목차를 생성해야 한다고 가정해 보겠습니다. 제목 요소는 HTML의 여러 위치에 나타날 수 있으므로 재귀 함수를 사용하여 이를 가져올 수 있는 방법이 없습니다. 기존 DOM은 다음 방법을 사용할 수 있습니다.

코드 복사 코드는 다음과 같습니다.var allElements = document .getElementsByTagName(' *'); for( var i = 0; i < allElements.length; i ) { if( allElements[i].tagName.match(/^h[2-4]$/i) ) { ... } }
웹페이지에 2000개 이상의 요소가 있는 경우 이 방법은 속도가 매우 느립니다. XPath가 지원되면 해석해야 하는 JavaScript보다 XPath 쿼리 엔진이 더 잘 최적화될 수 있으므로 훨씬 더 빠른 방법을 사용할 수 있습니다. 어떤 경우에는 XPath가 2배 이상 더 빠를 수도 있습니다. 다음 코드는 위와 동일한 기능을 수행하지만 XPath를 사용하므로 더 빠릅니다.

코드 복사 코드는 다음과 같습니다. 다음: varheadings = document.evaluate('//h2|//h3|//h4', document, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null ) var oneheading; () ) { .. }

下面版本代码融合上述两种方法;在支持 XPath 的地方使用快速方法,在不支持时使用传统 DOM 方法:

if( document.evaluate ) { 
var headings = document.evaluate( '//h2|//h3|//h4', document, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null ); 
var oneheading; 
while( oneheading = headings.iterateNext() ) { 
... 
} 
} else { 
var allElements = document.getElementsByTagName('*'); 
for( var i = 0; i < allElements.length; i++ ) { 
if( allElements[i].tagName.match(/^h[2-4]$/i) ) { 
... 
} 
} 
} 
로그인 후 복사

避免在遍历 DOM 时修改 DOM

有些 DOM 集合是实时的,如果在你的脚本遍历列表时相关元素产生变化,则此集合会立刻变化而不需要等待脚本遍历结束。childNodes 集合和 getElementsByTagName 返回的节点列表都是这样的实时集合。

如果在遍历这样的集合的同时向其中添加元素,则可能会遇到无限循环,因为你不停的向列表中添加元素,永远也不会碰到列表结束。这不是唯一的问题。为提高性能,可能会对这些集合做出优化,如记住其长度、记住脚本中上一个访问元素序号,这样在你访问下一个元素时可快速定位。

如果你此时修改 DOM 树,即使修改的元素不在此集合中,集合还是会重新搜索以查看是否有新元素。这样就无法记住上一个访问元素序号或记住集合长度,因为集合本身可能已经变了,这样就无法使用优化:

var allPara = document.getElementsByTagName('p'); 
for( var i = 0; i < allPara.length; i++ ) { 
allPara[i].appendChild(document.createTextNode(i)); 
} 
로그인 후 복사

下面的代码在 Opera 和 Internet Explorer 等主流浏览器中比上面代码快10倍以上。先创建一个要修改元素的静态列表,然后遍历静态列表并作出相应修改,而不是遍历 getElementsByTagName 返回的节点列表:

var allPara = document.getElementsByTagName('p'); 
var collectTemp = []; 
for( var i = 0; i < allPara.length; i++ ) { 
collectTemp[collectTemp.length] = allPara[i]; 
} 
for( i = 0; i < collectTemp.length; i++ ) { 
collectTemp[i].appendChild(document.createTextNode(i)); 
} 
collectTemp = null; 
로그인 후 복사

使用变量保存 DOM 值

有些 DOM 返回值无法缓存,每次调用时都会重新调用函数。如 getElementById 方法。下面是一个低效率代码的例子:

复制代码 代码如下:
document.getElementById('test').property1 = 'value1'; document.getElementById('test').property2 = 'value2'; document.getElementById('test').property3 = 'value3'; document.getElementById('test').property4 = 'value4';

此代码为定位同一个对象调用了四次 getElementById 方法。下面的代码只调用了一次并将结果保存在变量中,单看这一个操作可能比上面单个操作要略慢,因为需要执行赋值语句。但后面不再需要调用 getElementById 方法!下面的代码比上面的代码要快5-10倍:

复制代码 代码如下:
var sample = document.getElementById('test'); sample.property1 = 'value1'; sample.property2 = 'value2'; sample.property3 = 'value3'; sample.property4 = 'value4';

页面载入

避免保存来自其他文档的引用

如果文档访问过其他文档中的节点或对象,在脚本结束后避免保留这些引用。如果在全局变量或对象属性中保存过这些引用,通过设置为 null 清除之或者直接删除之。

原因是另一个文档被销毁后,如弹出窗口被关闭,尽管那个文档已经不再了,所有对那个文档中对象的引用都会在内存中保存整个 DOM 树和脚本环境。这也适用那些包含在frame,内联 frame,或 OBJECT 元素中的网页。.

复制代码 代码如下:
var remoteDoc = parent.frames['sideframe'].document; var remoteContainer = remoteDoc.getElementById('content'); var newPara = remoteDoc.createElement('p'); newPara.appendChild(remoteDoc.createTextNode('new content')); remoteContainer.appendChild(newPara); //remove references remoteDoc = null; remoteContainer = null; newPara = null;

빠른 기록 탐색

Opera(및 기타 여러 브라우저)는 기본적으로 빠른 기록 탐색을 사용합니다. 사용자가 뒤로 또는 앞으로 클릭하면 현재 페이지의 상태와 페이지의 스크립트가 기록됩니다. 사용자가 이전 페이지로 돌아오면 마치 이 페이지를 떠난 적이 없는 것처럼 이전 페이지가 즉시 표시됩니다. 페이지를 다시 로드하거나 다시 초기화할 필요가 없습니다. 스크립트는 계속 실행되며 DOM은 이 페이지를 떠나기 전과 완전히 동일합니다. 이는 사용자에게 더 반응성이 뛰어나며 로딩 속도가 느린 웹 애플리케이션의 성능이 향상됩니다.

Opera는 개발자가 이 동작을 제어할 수 있는 방법을 제공하지만 빠른 기록 검색 기능을 최대한 유지하는 것이 가장 좋습니다. 즉, 양식을 제출할 때 양식 컨트롤을 비활성화하거나 페이지 콘텐츠를 투명하거나 보이지 않게 만드는 페이드 아웃 효과를 포함하여 이 기능을 방해하는 작업을 피하는 것이 가장 좋습니다.

간단한 해결책은 onunload 리스너를 사용하여 페이드 아웃 효과를 재설정하거나 양식 컨트롤을 다시 활성화하는 것입니다. Firefox 및 Safari와 같은 일부 브라우저의 경우 unload 이벤트에 대한 리스너를 추가하면 기록 탐색이 비활성화됩니다. Opera에서 제출 버튼을 비활성화하면 기록 검색이 비활성화됩니다.

코드 복사 코드는 다음과 같습니다.
window.onunload = function() { document.body. 스타일.불투명도 = '1' };
XMLHttpRequest 사용

이 기술은 모든 프로젝트에 적합하지 않을 수 있지만 서버에서 다운로드되는 데이터의 양을 크게 줄이고 페이지를 다시 로드할 때 스크립트 환경을 파괴하고 생성하는 오버헤드를 피할 수 있습니다. 페이지를 정상적으로 로드한 다음 XMLHttpRequest를 사용하여 최소한의 새 콘텐츠를 다운로드합니다. 이렇게 하면 JavaScript 환경이 항상 존재하게 됩니다.

이 방법도 문제가 발생할 수 있다는 점에 유의하세요. 먼저 이 방법은 기록 탐색을 완전히 파괴합니다. 이 문제는 인라인 프레임에 정보를 저장하여 해결할 수 있지만 이는 분명히 XMLHttpRequest를 사용하는 원래 목적을 무너뜨리는 것입니다. 따라서 이전 콘텐츠로 돌아갈 필요가 없을 때만 아껴서 사용하세요. 이 접근 방식은 DOM이 변경되었음을 인식하지 못하기 때문에 보조 장치의 사용에도 영향을 미치므로 문제가 발생하지 않는 경우 XMLHttpRequest를 사용하는 것이 가장 좋습니다.

JavaScript를 사용할 수 없거나 XMLHttpRequest를 지원하지 않는 경우에도 이 트릭은 실패합니다. 이 문제를 피하는 가장 쉬운 방법은 일반 링크를 사용하여 새 페이지를 가리키는 것입니다. 링크가 활성화되었는지 여부를 감지하는 이벤트 핸들러를 추가합니다. 핸들러는 XMLHttpRequest가 지원되는지 여부를 감지할 수 있으며 지원되는 경우 새 데이터를 로드하고 링크의 기본 작업을 방지합니다. 새 데이터를 로드하고 페이지 일부를 교체한 후 요청 개체가 삭제되고 가비지 수집기가 메모리 리소스를 회수할 수 있습니다.

코드 복사 코드는 다음과 같습니다.
document.getElementById('nextlink').onclick = function () { if( !window.XMLHttpRequest ) { return true; } var request = new XMLHttpRequest(); request.onreadystatechange = function () { if( request.readyState != 4 ) { return; .replace( /^[wW]*
|
s*[wW]*$/g , '' ) document.getElementById('container ').innerHTML = useResponse = null; request.open( 'GET', this.href, true )
SCRIPT 요소를 동적으로 생성

스크립트를 로드하고 처리하는 데 시간이 걸리지만 일부 스크립트는 로드 후 전혀 사용되지 않습니다. 이러한 스크립트를 로드하면 시간과 리소스가 낭비되고 현재 스크립트 실행에 영향을 미치므로 사용하지 않는 스크립트는 참조하지 않는 것이 가장 좋습니다. 간단히 스크립트를 로드하여 어떤 스크립트가 필요한지 결정할 수 있으며, 나중에 필요한 스크립트에 대해서만 스크립트 요소를 생성할 수 있습니다.

이론적으로 이 로딩 스크립트는 페이지가 로드된 후 SCRIPT 요소를 생성하여 DOM에 추가할 수 있습니다. 이는 모든 주요 브라우저에서 잘 작동하지만 스크립트 자체를 로드하는 것보다 브라우저에 더 많은 요구 사항을 부과할 수 있습니다. 그리고 페이지가 로딩되기 전에 스크립트가 필요할 수 있으므로, 페이지 로딩 과정에서

를 통해 스크립트 태그를 생성해 두는 것이 가장 좋습니다. 현재 스크립트가 종료되는 것을 방지하려면 '/' 문자를 이스케이프 처리해야 합니다. document.write

코드 복사 코드는 다음과 같습니다.
if( document.createElement && document.childNodes ) { document.write('') } if( window.XMLHttpRequest ) { document.write('') }

location.replace() 제어 이력 항목

때때로 스크립트를 통해 페이지 주소를 수정해야 하는 경우가 있습니다. 일반적인 방법은 location.href에 새 주소를 할당하는 것입니다. 그러면 새 링크를 여는 것처럼 새 기록 항목이 추가되고 새 페이지가 로드됩니다.

사용자가 이전 페이지로 돌아갈 필요가 없기 때문에 새 기록 항목을 추가하고 싶지 않은 경우도 있습니다. 이는 메모리 리소스가 제한된 장치에 유용합니다. 기록 항목을 교체하여 현재 페이지에서 사용되는 메모리를 복구합니다. 이는 location.replace() 메소드를 통해 달성할 수 있습니다.

코드 복사 코드는 다음과 같습니다.
location.replace('newpage.html')

페이지는 여전히 캐시에 저장되어 있으며 여전히 메모리를 차지하지만 기록에 저장된 것보다 훨씬 적습니다.

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