JavaScript 高級程序設計之最佳實踐
一、可維護性:可理解性、直觀性、可適應性、可擴展性、可調試性
- 代碼約定:
- 可讀性
- 格式化:建議縮進大小為4個空格
- 注釋:函數和方法、大段代碼、復雜的算法、hack </ol>
- 變量和函數命名
- 變量名為名詞
- 函數名為動詞開始
- 變量和函數使用合符邏輯的名字,不要擔心長度。 </ol>
- 變量類型透明:表示變量類型的三種方式
- 初始化: var found = false; //布爾型
- 使用匈牙利標記法來指定變量類型:o代表對象,s代表字符串,i代表整數,f代表浮點數,b代表布爾型
- 使用類型注釋: var found /*:Boolen*/ = false; </ol> </ol>
- 松散耦合:
- 解耦HTML/JavaScript
- HTML中包含JavaScript,示例:<script type="text/javascript">document.write("hello world!")</script>; // <script>標簽緊密耦合
<input type="button" value="Click me " onclick="doSomething();"/> //事件屬性值緊密耦合 - 理想情況:HTML和JavaScript應該完全分離,并通過外部文件和使用DOM附加行為來包含JavaScript。
問題:出現JavaScript錯誤就要判斷是在HTML中還是在JavaScript中,且在doSomething()可用之前就按下button,也會引發JavaScript錯誤。 - JavaScript中包含HTML,JavaScript生成HTML,這個應該避免,保持層次的分離有助于很容易的確定錯誤來源。
- 理想情況:JavaScript用于插入數據時,盡量不直接插入標記,可以控制標記的顯示和隱藏,而非生成它。另一種方法是進行Ajax請求并獲取更多要顯示的HTML,這個方法可以讓同樣的渲染層(PHP、JSP、Ruby)來輸出標記。 </ol>
- 解耦CSS/JavaScript
- 利用JavaScript修改樣式時,應該通過動態修改樣式類而非特定樣式來實現。
- 顯示問題的唯一來源應該是CSS,行為問題的唯一來源應該是JavaScript。 </ol>
- 解耦應用邏輯/事件處理程序
- 應用邏輯和事件處理程序相分離,一個事件處理程序應該從事件對象中獲取相關信息,并將這些信息傳送到處理應用程序的某個方法中。
- 好處:可以更容易更改觸發特定過程的事件;其次可以在不附加到事件的情況下測試代碼,使其更易創建單元測試或者是自動化應用流程。
- 應用和業務邏輯之間松散耦合的幾條原則:
- 勿將event對象傳給其他方法;只傳來自event對象中所需的數據;
- 任何在應用層面的動作都應該可以在不執行任何事件處理程序的情況下進行。
- 任何事件處理程序都應該處理事件,然后將處理轉交給應用邏輯。 </ol> </ol> </ol>
- 編程實踐:
- 尊重對象所有權:如果你不負責創建和維護某個對象、它的對象或者它的方法,那么你就不能對它們進行修改。
- 不要為實例或者原型添加屬性;
- 不要為實例或者原型添加方法;
- 不要重定義已存在的方法。 </ol>
- 避免全局變量:最多創建一個全局量,讓其他對象和函數存在其中。
- 避免與null進行比較
- 如果值應為一個引用類型,使用instanceof操作符檢查其構造函數。
- 如果值應為一個基本類型,使用typeof檢查其類型。
- 如果是希望對象包含某個特定的方法名,則使用typeof操作符確保指定名稱的方法存在于對象上。 </ol>
- 使用常量
- 關鍵在于將數據和使用它的邏輯進行分離
- 重復值:任何在多處用到的值都應抽取為一個常量,也包含css類名,這就限制了當一個值變了而另一個沒變的時候會造成的錯誤。
- 用戶界面字符串:方便國際化
- URLs:在web應用中,資源位置很容易變更,所以推薦用一個公共地方存放所有的URL
- 任意可能會更改的值 </ol> </ol> </ol> </div>
- 注意作用域:
- 避免全局查找:使用全局變量和函數肯定要比局部的開銷更大,因為涉及作用域鏈上的查找。
- 示例代碼:
function updateUI(){
var imgs = document.getElementsByTagName("img");
for(var i=0,len=imgs.length;i<len;i++)
{
imgs[i].title = document.title + " image " + i;
}
var msg = document.getElementById("msg");
msg.innerHTML = "Update complete.";
}
- 優化后的代碼
function updateUI(){
var doc = document;
var imgs = doc .getElementsByTagName("img");
for(var i=0,len=imgs.length;i<len;i++)
{
imgs[i].title = doc .title + " image " + i;
}
var msg = doc .getElementById("msg");
msg.innerHTML = "Update complete.";
}
</ol>
- 避免with語句:在性能非常重要的地方必須避免使用with語句。
- 和函數類似,with語句會創建自己的作用域,肯定會增加其中執行的代碼的作用域鏈的長度。
- 必須使用with語句的情況很少,它主要用于消除額外的字符。在大多數情況下,可以用局部變量完成相同的事情而不用引入新的作用域。
- 實例代碼:
function updateBody(){
with(document.body){
alert(tagName);
innerHTML = "hello world!";
}
}
改進后的代碼:
function updateBody(){
var body = document.body;
alert(body.tagName);
body.innerHTML = "hello world!";
}
</ol>
</ol>
- 選擇正確方法
- 避免不必要的屬性查找
- 常數值O(1):指代字面值和存儲在變量中的值,訪問數組元素
- 對數值O(log n):
- 線性O(n):訪問對象,對象上的任何屬性查找都要比訪問變量或者數組花費更長時間,因為必須在原型鏈中對擁有該名稱的屬性進行一次搜索,屬性查找越多,執行時間久越長。
一旦多次用到對象屬性,應該將其存儲在局部變量中。 - 平方O(n2): </ol>
- 優化循環:基本步驟如下:
- 減值迭代:在很多情況下,從最大值開始,在循環中不斷減值的迭代器更加高效。
- 簡化終止條件:由于每次循環過程都會計算終止條件,所以必須保證它盡可能快。也就是說避免屬性查找或者其他O(n)的操作。
- 簡化循環體:循環體是執行最多的,所以要確保其被最大限度的優化。確保沒有某些可以被很容易移除循環的密集計算。
- 使用后測試循環:最常用的for循環和while循環都是前測試循環,而如do-while這種后測試循環,可以避免最初終止條件的計算,因此運行更快。
- 示例代碼:
for(var i=0; i < values.length; i++){
process(value[i]);
}
減值迭代優化:
for(var i=values.length; i >= 0 ; i--){
process(value[i]);
}
后測試循環優化:記住使用后測試循環時必須確保要處理的值至少有一個,空數組會導致多余的一次循環而前測試循環則可以避免。
var i = values.length - 1;
if(i > -1){
do{
process(values[i]);
}while(--i > 0);
}
</ol>
- 展開循環
- 當循環的次數是確定的,消除循環并使用多次函數調用往往更快。
- 如果循環中的迭代次數不能事先確定,可以使用duff裝置技術,它以創建者Tom Duff命名,并最早在C語言中使用這項技術。Jeff Greenberg 用JavaScript實現了Duff裝置,基本概念是通過計算迭代的次數是否為8的倍數將一個循環展開為一系列語句。
- Jeff Greenberg的Duff裝置技術代碼:通過將values數組中元素個數除以8來計算出循環需要進行多次迭代的。
//credit: Jeff Greenberg for JS implementation of Duff's Device
//假設values.length > 0
var iterations = Math.ceil(values.length / 8);
var startAt = values.length % 8;
var i = 0;
do{
switch(startAt){
case 0: process(values[i++]);
case 1: process(values[i++]);
case 2: process(values[i++]);
case 3: process(values[i++]);
case 4: process(values[i++]);
case 5: process(values[i++]);
case 6: process(values[i++]);
case 7: process(values[i++]);
}
startAt = 0;
} while (--iterations > 0);
- 由Andrew B.King 所著的Speed Up your Site(New Riders,2003),提出了一個更快的Duff裝置技術,將do-while循環分成2個單獨的循環。一下是例子:
//credit: Speed Up your Site(New Riders,2003)
var iterations = Math.floor(values.length / 8);
var leftover = values.length % 8;
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()的主循環,這個方法幾乎比原始的Duff裝置實現快上40%。
- 針對大數據集使用展開循環可以節省很多時間,不過對于小數據集,額外的開銷則可能得不償失。
</ol>
- 避免雙重解釋:當JavaScript代碼想解析JavaScript的時候就會存在雙重解釋懲罰。當使用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); - 性能的其他注意事項:
- 原生方法較快:原生方法是用諸如C/C++之類的編譯型語言寫出來的,所以要比JavaScript快的很多很多。JavaScript最容易被忘記的就是可以在Math對象中找到的復雜的數學運算,這些方法要比任何用JavaScript的同樣方法如正弦、余弦快的多。
- Switch語句較快
- 位運算符較快:取模、邏輯與和邏輯或 </ol>
</ol>
- 最小化語句數:JavaScript代碼中的語句數量也影響所執行的操作的速度。完成多個操作的單個語句要比完成單個操作的多個語句快。
- 多個變量聲明
- 插入迭代值
- 使用素組和對象字面量 </ol>
- 優化DOM交互
- 最小化現場更新
- 現場更新:需要立即(現場)對頁面對用戶的顯示進行更新。每一個更改,不管是插入單個字符,還是移除整個片段,都有一個性能懲罰,因為瀏覽器要重新計算無數尺寸以進行更新。
- 實例代碼:
var list = document.getElementById("myList"),
item,
i;
for(i = 0; i < 10;i ++){
item = document.createElement("li");
list = appendChild(item);
item.append(document.createTextNode("Item " + i));
}
分析:該代碼添加每個項目時都有2個現場更新:一個添加<li>元素,另一個給它添加文本節點。總共需要20個現場更新。兩種優化方法:第一種將列表從頁面上移除,最后進行更新,最后再將列表插回到同樣的位置,這個方法不理想,因為每次頁面更新的時候會不必要的閃爍。第二個方法是使用文檔片段來構建DOM結構,接著將其添加到List元素中,這種方法避免了現場更新和頁面閃爍問題。
優化后的代碼:
var list = document.getElementById("myList"),
fragment.document.createDocumentFragment(),
item,
i;
for(i = 0; i < 10;i ++){
item = document.createElement("li");
fragment.appendChild(item);
item.appendChild(document.createTextNode("Item " + i));
}
list.appendChild(fragment);
</ol>
- 使用innerHTML:
- 頁面中創建DOM節點的方法有:使用諸如createElement()和appendChild()之類的DOM方法,以及使用innerHTML。對于小的DOM更改而言,兩種方法效率都差不多。然而對于大的DOM更改,使用innerHTML要比使用標準DOM方法創建同樣的DOM結構快得多。
- 因為當把innerHTML設置為某個值時,后臺會創建一個HTML解析器,然后使用內部的DOM調用來創建DOM結構,而非基于JavaScript的DOM調用。由于內部方法是編譯好的而非解釋執行的,所以執行快的多。
- 調用innerHTML(和其他DOM操作一樣)關鍵在于最小化調用它的次數。 </ol>
- 使用事件代理
- 頁面上的事件處理程序的數量和頁面響應用戶交互的速度之間有個負相關,為了減輕這種懲罰,最好使用事件代理。
- 事件代理用到了事件冒泡,任何可以冒泡的事件都不僅僅可以在事件目標上進行處理,目標的任何祖先節點上也能處理,因此可以將事件處理程序附加到更高層的地方負責多個目標的事件處理,如果在文檔級別附加事件處理程序,就可以處理整個頁面的事件。 </ol>
- 注意HTMLCollection
- 訪問HTMLCollection,不管它是一個屬性還是一個方法,都是在文檔上進行一個查詢,而且這個查詢開銷很昂貴,最小化訪問HTMLColletcion的次數可以極大地改進腳本性能。
- 優化HTMLCollection訪問最重要的地方在于循環
實例代碼:
var images = document.getElementsByTagName("img"),image,i,len;
for(i=0,len=images.length;i < len;i++){
image = images[i];
//處理
} - 何時會返回HTMLCollection對象:
- 進行了對getElementsByTagName()的調用
- 獲取了元素的childNodes屬性
- 獲取了元素的attributes屬性
- 訪問了特殊的集合,如document.forms、document.images等。
</ol>
</ol>
</ol>
</ol>
</div>
</div>
來自:http://my.oschina.net/jieqiuming/blog/339920
二、保證代碼性能
本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!