1. 범위에 주의하세요
전역 검색 방지
예:
function updateUI(){ var imgs = document.getElementByTagName("img"); for(var i=0, len=imgs.length; i<len; i++){ imgs[i].title = document.title + " image " + i; } var msg = document.getElementById("msg"); msg.innnerHTML = "Update complete."; }
이 함수는 완전히 정상적으로 보일 수 있지만 세 개의 전역 문서가 포함되어 있습니다. 개체. 페이지에 여러 이미지가 있는 경우 for 루프의 문서 참조는 여러 번 또는 수백 번 실행되고 매번 범위 체인 검색이 수행됩니다. 문서 개체를 가리키는 로컬 변수를 생성하면 전역 검색을 제한하여 이 기능의 성능을 향상시킬 수 있습니다.
function updateUI(){ var doc = document; var imgs = doc.getElementByTagName("img"); for(var i=0, len=imgs.length; i<len; i++){ imgs[i].title = doc.title + " image " + i; } var msg = doc.getElementById("msg"); msg.innnerHTML = "Update complete."; }
여기서 먼저 문서 개체를 로컬 doc 변수에 저장한 다음 나머지 부분에 저장합니다. 코드 원본 문서를 교체하십시오. 원래 버전과 비교하여 이 함수에는 이제 전역 조회가 하나만 포함되어 있어 확실히 더 빠릅니다.
2. 올바른 방법 선택
1. 불필요한 속성 조회를 피하세요
상수 값을 얻는 것은 매우 효율적인 프로세스입니다
var value = 5; var sum = 10 + value; alert(sum);
4개의 상수 값 검색: 숫자 5, 변수 값, 숫자 10 및 변수 합계.
JavaScript에서 배열 요소에 액세스하는 것은 간단한 변수 조회만큼 효율적입니다. 따라서 다음 코드는 이전 예제만큼 효율적입니다.
var value = [5,10]; var sum = value[0] + value[1]; alert(sum);
객체에 대한 모든 속성 조회는 변수나 배열에 액세스하는 것보다 시간이 더 오래 걸립니다. 해당 이름을 가진 속성에 대한 검색은 프로토타입에서 수행되어야 하기 때문입니다. 체인. 속성 조회가 많을수록 실행 시간이 길어집니다.
var values = {first: 5, second: 10}; var sum = values.first + values.second; alert(sum);
이 코드는 두 가지 속성 조회를 사용하여 합계 값을 계산합니다. 속성 조회를 한두 번 수행하면 심각한 성능 문제가 발생하지 않지만 수백 또는 수천 번 수행하면 확실히 실행 속도가 느려집니다.
단일 값을 얻는 여러 속성 조회에 유의하세요. 예:
var query = window.location.href.substring(window.location.href.indexOf("?"));
이 코드에는 6개의 속성 조회가 있습니다. 즉, window.location.href.substring()에 대해 3회, window.location.href.indexOf()에 대해 3회입니다. 조회 횟수를 결정하려면 코드의 포인트 수를 세면 됩니다. 이 코드는 window.location.href를 두 번 사용하고, 동일한 검색을 두 번 수행하므로 효율성이 특히 떨어집니다.
객체 속성이 여러 번 사용되면 지역 변수에 저장해야 합니다. 이전 코드는 다음과 같이 다시 작성할 수 있습니다.
var url = window.locaiton.href; var query = url.substring(url.indexOf("?"));
이 버전의 코드에는 속성 조회가 4개만 있어서 원래 버전에 비해 33%가 절약됩니다.
일반적으로 알고리즘의 복잡도를 줄일 수 있는 만큼 최대한 줄여야 합니다. 지역 변수를 사용하여 속성 조회를 값 조회로 최대한 대체하고, 숫자 배열 위치나 명명된 속성(예: NodeList 개체)을 사용하여 액세스할 수 있는 경우 숫자 위치를 사용하세요.
2. 최적화 루프
루프의 기본 최적화 단계는 다음과 같습니다.
(1) 감소 반복 - 대부분의 루프는 0에서 시작하여 특정 값으로 증가하는 반복기를 사용합니다. 대부분의 경우 최대값부터 시작하여 루프를 통해 반복자를 감소시키는 것이 더 효율적입니다.
(2) 종료 조건 단순화 - 루프 처리가 수행될 때마다 종료 조건이 계산되므로 최대한 빠른 속도가 보장되어야 합니다. 이는 속성 조회나 기타 작업을 피하는 것을 의미합니다.
(3) 루프 본문을 단순화합니다. 루프는 가장 많이 실행되므로 최대한 최적화되었는지 확인하고 루프의 다른 집중적인 계산을 쉽게 제거할 수 있는지 확인하세요.
(4 사후 테스트 루프 사용 - 가장 일반적으로 사용되는 for 루프 및 while 루프는 사전 테스트 루프입니다. do-while과 같은 사후 테스트 루프는 초기 종료 조건 계산을 피할 수 있으므로
다음은 기본 for 루프입니다.
for(var i=0; i < value.length; i++){ process(values[i]); }
이 코드에서 변수 i는 0에서 값 배열의 총 요소 수까지 증가하며, 루프는 아래와 같이 i를 감소시키도록 변경할 수 있습니다. :
for(var i=value.length -1; i >= 0; i--){ process(values[i]); }
종료 조건은 value.length에서 0으로 단순화됩니다.
루프는 테스트 후 루프로 변경될 수도 있습니다.
var i=values.length -1; if (i> -1){ do{ process(values[i]) }while(--i>=0) //此处有个勘误,书上终止条件为(--i>0),经测试,(--i>=0)才是正确的 }
여기서 주요 최적화는 종료 조건과 감소 연산자가 단일 명령문으로 결합되었으며 루프 부분이 완전히 최적화되었습니다.
"사후 테스트" 루프에서는 처리할 값이 하나 이상 있는지 확인해야 합니다. "사전 테스트" 루프를 사용하면 추가 루프를 피할 수 있습니다.
3. 루프
루프 수가 결정되면 루프를 제거하고 여러 함수 호출을 사용하는 것이 더 빠른 경우가 많습니다. 값 배열에 요소가 3개만 있다고 가정하고 각 항목에서 직접 process()를 호출합니다. 요소를 사용하면 루프를 설정하고 종료 조건을 처리하는 추가 오버헤드를 제거하여 코드 실행 속도를 높일 수 있습니다. 반복 횟수를 미리 결정할 수 없는 경우 Duff 장치라는 기술을 사용하는 것을 고려할 수 있습니다. 더프 장치는 반복 횟수가 8의 배수인지 계산하여 루프를 일련의 명령문으로 확장하는 것입니다. Andrew B. King은 do-while 루프를 2개의 개별 루프로 분할하는 더 빠른 Duff 장치 기술을 제안했습니다. 예:
var iterations = Math.floor(values.length / 8); var leftover = values.length % 8; var i = 0; if(leftover>0){ do{ process(values[i++]); }while(--leftover > 0); } do{ process(values[i++]); process(values[i++]); process(values[i++]); process(values[i++]); process(values[i++]); process(values[i++]); process(values[i++]); process(values[i++]); }while(--iterations > 0);
在这个实现中,剩余的计算部分不会在实际循环中处理,而是在一个初始化循环中进行除以8的操作。当处理掉了额外的元素,继续执行每次调用8次process()的主循环。
针对大数据集使用展开循环可以节省很多时间,但对于小数据集,额外的开销则可能得不偿失。它是要花更多的代码来完成同样的任务,如果处理的不是大数据集,一般来说不值得。
4.避免双重解释
当JavaScript代码想解析KavaScript的时候就会存在双重解释惩罚。当使用eval()函数或者是Function构造函数以及使用setTimeout()传一个字符串参数时都会发生这种情况。
//某些代码求值——避免!! eval("alert('Hello world!')"); //创建新函数——避免!! var sayHi = new Function("alert('Hello world!')"); //设置超时——避免!! setTimeout("alert('Hello world!')", 500);
在以上这些例子中,都要解析包含了JavaScript代码的字符串。这个操作是不能在初始的解析过程中完成的,因为代码是包含在字符串中的,也就是说在JavaScript代码运行的同时必须新启动一个解析器来解析新的代码。实例化一个新的解析器有不容忽视的开销,所以这种代码要比直接解析慢得多。
//已修正 alert('Hello world!'); //创建新函数——已修正 var sayHi = function(){ alert('Hello world!'); }; //设置一个超时——已修正 setTimeout(function(){ alert('Hello world!'); }, 500);
如果要提高代码性能,尽可能避免出现需要按照JavaScript解析的字符串。
5.性能的其他注意事项
(1)原生方法较快
(2)Switch语句较快
(3)位运算符较快
三. 最小化语句数
1.多个变量声明
//4个语句——很浪费 var count = 5; var color = "blue"; var values = [1,2,3]; var now = new Date(); //一个语句 var count = 5, color = "blue", values = [1,2,3], now = new Date();
2.插入迭代值
当使用迭代值的时候,尽可能合并语句。
var name = values[i]; i++;
前面这2句语句各只有一个目的:第一个从values数组中获取值,然后存储在name中;第二个给变量i增加1.这两句可以通过迭代值插入第一个语句组合成一个语句。
var name = values[i++];
3.使用数组和对象字面量
//用4个语句创建和初始化数组——浪费 var values = new Array(); values[0] = 123; values[1] = 456; values[2] = 789; //用4个语句创建和初始化对象——浪费 var person = new Object(); person.name = "Nicholas"; person.age = 29; person.sayName = function(){ alert(this.name); };
这段代码中,只创建和初始化了一个数组和一个对象。各用了4个语句:一个调用构造函数,其他3个分配数据。其实可以很容易地转换成使用字面量的形式。
//只有一条语句创建和初始化数组 var values = [13,456,789]; //只有一条语句创建和初始化对象 var person = { name : "Nicholas", age : 29, sayName : function(){ alert(this.name); } };
重写后的代码只包含两条语句,减少了75%的语句量,在包含成千上万行JavaScript的代码库中,这些优化的价值更大。
只要有可能,尽量使用数组和对象的字面量表达方式来消除不必要的语句。
四 .优化DOM交互
1.最小化现场更新
一旦你需要访问的DOM部分是已经显示的页面的一部分,那么你就是在进行一个现场更新。现场更新进行得越多,代码完成执行所花的事件就越长。
var list = document.getElementById('myList'), item, i; for (var i = 0; i < 10; i++) { item = document.createElement("li"); list.appendChild(item); item.appendChild(document.createTextNode("Item" + i)); }
这段代码为列表添加了10个项目。添加每个项目时,都有2个现场更新:一个添加li元素,另一个给它添加文本节点。这样添加10个项目,这个操作总共要完成20个现场更新。
var list = document.getElementById('myList'), fragment = document.createDocumentFragment(), item, i; for (var i = 0; i < 10; i++) { item = document.createElement("li"); fragment.appendChild(item); item.appendChild(document.createTextNode("Item" + i)); } list.appendChild(fragment);
在这个例子中只有一次现场更新,它发生在所有项目都创建好之后。文档片段用作一个临时的占位符,放置新创建的项目。当给appendChild()传入文档片段时,只有片段中的子节点被添加到目标,片段本身不会被添加的。
一旦需要更新DOM,请考虑使用文档片段来构建DOM结构,然后再将其添加到现存的文档中。
2.使用innerHTML
有两种在页面上创建DOM节点的方法:使用诸如createElement()和appendChild()之类的DOM方法,以及使用innerHTML。对于小的DOM更改而言,两种方法效率都差不多。然而,对于大的DOM更改,使用innerHTML要比使用标准DOM方法创建同样的DOM结构快得多。
当把innerHTML设置为某个值时,后台会创建一个HTML解析器,然后使用内部的DOM调用来创建DOM结构,而非基于JavaScript的DOM调用。由于内部方法是编译好的而非解释执行的,所以执行快得多。
var list = document.getElementById("myList"); html = ""; i; for (i=0; i < 10; i++){ html += "<li>Item " + i +"</li>"; } list.innerHTML = html;
使用innerHTML的关键在于(和其他的DOM操作一样)最小化调用它的次数。
var list = document.getElementById("myList"); i; for (i=0; i < 10; i++){ list.innerHTML += "<li>Item " + i +"</li>"; //避免!!! }
这段代码的问题在于每次循环都要调用innerHTML,这是极其低效的。调用innerHTML实际上就是一次现场更新。构建好一个字符串然后一次性调用innerHTML要比调用innerHTML多次快得多。
3.使用事件代理(根据第13章的概念,我认为此处应为“事件委托”更为妥当)
4.注意HTMLCollection
任何时候要访问HTMLCollection,不管它是一个属性还是一个方法,都是在文档上进行一个查询,这个查询开销很昂贵。
var images = document.getElementsByTagName("img"), image, i,len; for (i=0, len=images.length; i < len; i++){ image = images[i]; //处理 }
将length和当前引用的images[i]存入变量,这样就可以最小化对他们的访问。发生以下情况时会返回HTMLCollection对象:
进行了对getElementsByTagName()的调用;
获取了元素的childNodes属性;
获取了元素的attributes属性;
访问了特殊的集合,如document.forms、document.images等。