在進行JavaScript性能優化時,需要注意兩件事:不要做不必要的工作,不要重複做已經完成的工作。不要做不必要的工作就要重構代碼,降低冗余率。不要重複做已經完成的工作通常難以確定,因為工作可能因為各種原因而在很多地方被重複。
也許最常見的重複工作是瀏覽器檢測。大量代碼依賴於瀏覽器的功能。以事件句柄的添加和刪除為例,典型的跨瀏覽器代碼如下:
function addHandler(target,eventType,handler){
if(target.addEventListener){//DOM2 Events
target.addEventListener(eventType,handler,false);
}else{//IE
target.attachEvent("on"+eventType,handler);
}
}
function removeHandler(target,eventType,handler){
if(target.removeEventListener){//DOM2 Events
target.removeEventListener(eventType,handler,false);
}else{//IE
target.detachEvent("on"+eventType,handler);
}
}
在上面代碼中,通過測試addEventListener和removeEventListener來檢查DOM2的事件支持情況,它能夠被除IE之外的所有現代瀏覽器所支持。如果這些方法不存在於target中,那麼就認為當前瀏覽器是IE,並且使用IE特有的方法。
這兩個函數隱藏著性能問題:每次函數調用時都執行重複工作,檢查某種方法是否存在。在每次調用中重複同樣的工作是一種浪費。假設target的唯一值就是DOM對象,而且用戶不可能在頁面加載時改變瀏覽器,如果在調用addHandler時首先調用了addEventListener,那麼每個後續調用都要調用addEventListener。
(1)延遲加載
延遲加載就是在信息被使用之前不做任何工作。例如,在上面示例中不需要判斷使用哪種方法附加或分離事件句柄,直到函數被調用。
function addHandler(target,eventType,handler){
if(target.addEventListener){//DOM2 Events
addHandler=function(target,eventType,handler){
target.addEventListener(eventType,handler,false);
};
}else{//IE
addHandler=function(target,eventType,handler){
target.attachEvent("on"+eventType,handler);
};
}
addHandler(target,eventType,handler);
}
function removeHandler(target,eventType,handler){
if(target.removeEventListener){//DOM2 Events
removeHandler=function(target,eventType,handler){
target.addEventListener(eventType,handler,false);
};
}else{//IE
removeHandler=function(target,eventType,handler){
target.detachEvent("on"+eventType,handler);
};
}
removeHandler(target,eventType,handler);
}
這兩個函數在第一次被調用時檢查一次並決定使用哪種方法附加或分離事件句柄,然後原始函數就被包含適當操作的新函數覆蓋了,最後調用新函數並將原始參數傳給它。以後再調用addHandler或removeHandler時不會再次檢測,因為檢測代碼已經被新函數覆蓋了。
調用一個延遲加載函數總是在第一次時需要較長時間,因為必須在運行檢測後調用另一個函數以完成任務。後續調用同一函數將快很多,因為不再執行檢測了。延遲加載適用於函數不會在頁面上立即用到的場合。
(2)條件預加載
條件預加載就是在腳本加載之前提前進行檢查,而不用等待函數調用。這樣的檢測仍然只進行一次,但在此過程中來得更早,例如:
var addHandler=document.body.addEventListener?function(target,eventType,handler){
target.addEventListener(eventType,handler,false);
}:function(target,eventType,handler){
target.attachEvent("on"+eventType,handler);
};
var removeHandler=document.body.removeEventListener?function(target,eventType,handler){
target.removeEventListener(eventType,handler,false);
}:function(target,eventType,handler){
target.detachEvent("on"+eventType,handler);
};
在上面代碼中,先檢查addEventListener和removeEventListener是否存在,然後根據信息指定最合適的函數。三元操作符返回DOM級別2的函數,如果它們不存在,則返回IE特有的函數。雖然檢測功能提前了,但是接下來調用addHandler和removeHandler同樣很快。條件預加載確保所有函數調用時間相同,其代價是在腳本加載時進行檢測。預加載適用於一個函數馬上就會被用到且在整個頁面生命週期中經常會被使用的場合。