Javascript程式最佳化
來源:互聯網
上載者:User
繼續我們的 Javascript 最佳化計劃,上期已經做到怎麼儘可能的縮小 Javascript 指令碼的檔案體積便於傳輸。不過這樣做僅僅是不夠的,因為 Javascript 代碼的速度被分割成兩部分:下載時間(取決於檔案的大小)和執行速度(取決於代碼演算法)。當用戶端載入 Javascript 指令碼以後,真正的之行速度就取決於代碼本身是否最佳化了。這篇就是講述如何最佳化代碼本身的執行速度(聽起來非常有技術的樣子)。關注範圍瀏覽器中,Javascript 預設的變數範圍是 window,也就是全域變數。在 window 中的變數只在頁面從瀏覽器關閉以後才釋放。而 Javascript 同時也有局部變數(私人變數)的概念,通常它在容器(比如 function)中執行完畢就會被釋放。所以很容易理解當調用某變數時,解譯器就會自下(容器)由上(window)尋找變數,尋找的變數本身也是需要一點時間的。所以,解譯器在作用樹(《Javascript 進階程式設計》中稱為“範圍樹”)中遍曆的範圍越短,那麼指令碼運行就會越快。本人不擅長施教,下面的代碼請自行理解var country = "China";function fn1() { alert(country);}function fn2() { var province = "Zhejiang"; fn1();}function fn3() { var city = "Hangzhou"; fn2();}fn3();擴充閱讀請點擊這裡和這裡。使用局部變數理解了上述的細節以後,接下來就非常可以理解了。使用局部變數可以帶來更快的執行速度,因為解譯器無需因為搜尋變數而離開當前執行範圍。同時,局部變數讓允許完畢就會被釋放,所以它們不會一直佔用記憶體。這裡要注意的是,使用閉包會打破這一規則,詳細資料可以參看這裡和以前我做的一道題目。避免使用 with 語句搜尋變數範圍越小,運行速度越快,所以就很很容易理解避免使用 with 語句的原因。比如alert(document.title);alert(document.body.tagName);alert(document.location);可以寫成with (document) { alert(title); alert(body.tagName); alert(location);}雖然代碼縮減的程度,並且也非常的容易理解。但是使用 with 語句的同時,要強制解譯器不僅在作用樹(範圍樹)內尋找局部變數,還強制檢測每個變數及指定的對象,看其是否有此變數或者屬性。因此,最好避免使用 with 語句。最短的代碼並不一定總是最高效的。選擇正確的演算法這似乎就是廢話,所有的程式員都明白正確的演算法對於之行效率是多麼的重要。這裡就不多解釋,可以參考這篇和這篇。我始終相信,好的經驗都是在實際 coding 中獲得的。迴圈的花招Javascript 和大部分的程式語言一樣,迴圈都會花費大量的執行時間,所以保持迴圈的高效可以減少執行時間。下面有幾個花招,也是從那本書中獲得的,照本宣科一下。反轉迴圈有一個很有趣的例子,比如一個典型的迴圈會是這樣寫for (var i = 0; i < element.length; i++) { // ...}但寫成下面這個樣子就有助於降低演算法的複雜度,因為它用常數(O)作為條件迴圈以減少執行時間for (var i = element.length - 1; i >= 0; i--) { // ...}書中的解釋可能無法理解,那麼我重新將其寫成var element_length = element.length;for (var i = 0; i < element_length; i++) { // ...}可能會更好理解一些,因為它不會重複在迴圈中擷取 element 的 length 屬性,但書中的更改方法少了一個變數。翻轉迴圈用 do...while 來替代 while 語句可以進一步的減少執行時間。比如var i = 0;while (i < element.length) { // ... i++;}可以改寫為 do...while 語句為這個樣子var i = 0;do { // ... i++;} while (i < element.length);當然,按照上一條的花招我們還可以最佳化成這個樣子var i = element.length - 1;do { // ...} while (--i >= 0);這是因為 do...while 語句事先將迴圈體載入以後再做條件判斷。不過本人認為還是保持程式的邏輯優先。條件判斷最佳化 if 語句用 if 和多個 else 語句時,將就有可能的情況放在最先,依次類推。同時盡量減少 else 和 if 的數量,將條件按照二叉樹的方式進行排列。例如if (i > 0 && i < 10) { alert('between 0 and 10');} else if (i > 9 && i < 20) { alert('between 10 and 20');} else if (i > 19 && i < 30) { alert('between 19 and 30');} else { alert('out of range');}可以將這段代碼寫成if (i > 0) { if (i < 10) { alert('between 0 and 10'); } else { if (i < 20) { alert('between 10 and 20'); } else { if (i < 30) { alert('between 20 and 30'); } else { alert('Greater than or equal 30'); } } }} else { alert('less than or equal 0');}這個樣子。雖然看上去非常的複雜,但是它已經考慮了很多代碼潛在的條件判斷情況,所以執行得更快。switch 和 if用 switch 還是 if 已經是老生常談的問題了。一般來說,超過兩個 if...else 判斷的時候,最好是使用 switch 語句。這樣做可以使代碼更加清晰並且效率更高。同時,case 條件也可以使用任何類型的值。語句瘦身其實非常可以容易理解,指令碼中的語句越少,執行所需的時間越短(聽起來與上述觀點有矛盾)。有很多方法可以將代碼中的語句縮短,比如下面的一些花招。定義多個變數很明顯,一條語句可以定義多個變數。這樣做不僅可以縮小代碼體積,還可以減少語句數量以減少執行時間。比如下面的代碼var webSite = "www.gracecode.com";var haveLunch = function () {...}; 就可以精簡為var webSite = "www.gracecode.com", haveLunch = function () {...};相信這樣的語句也不會給閱讀帶來多大的障礙。迭代因子使用迭代因子,儘可能的合并語句。比如var girlFriend = girl[i];i++;這樣的語句可以使用var girlFriend = girl[i++];替代。不過建議特別小心 i++ 和 ++i 的區別(該死的 C 語言後遺症)。使用數組和對象字面量這點其實在上一篇的時候就提到過,在這裡就不複述。比如var mySite = new Object;mySite.author = "feelinglucky";mySite.location = "http://www.gracecode.com";就可以精簡到var mySite = {author:"feeinglucky", location:"http://www.gracecode.com"};這樣子。其他的花招優先使用內建方法比如function power(number, n) { var result = number; for (var i = 1; i < n; i++) { result *= number; } return result;}這樣的函數,完全就可以使用 Math.pow 來完成。Javascript 已經有很多現成的內建方法,只要允許最好使用它們。儲存常用的值當多次用到同一個值的時候,可以先將其儲存在局部變數中,以便快速存取。這個就不複述了,偷個懶不好意思。DOM 操作節約 DOM 操作Javascript 對 DOM 的處理,可能是最耗費時間的操作之一。每次 Javascript 對 DOM 的操作,瀏覽器都會改變頁面的表現、重新渲染頁面,從而有明顯的時間損耗。比較環保的做法就是儘可能不在 DOM 中進行 DOM 操作。請看下面的例子,為 ul 添加 10 個條目var oUl = document.getElementById("ulItem");for (var i = 0; i < 10; i++) { var oLi = document.createElement("li"); oUl.appendChild(oLi); oLi.appendChild(document.createTextNode("Item " + i);}乍看起來似乎無懈可擊,但是這段代碼的確有問題。首先是迴圈中的 oUl.appendChild(oLi); 的調用,每次執行過後瀏覽器就會重新渲染頁面;其次,給列表添加添加文本節點(oLi.appendChind(document.createTextNode("Item " + i);),也這會造成頁面重新渲染。每次運行都會造成兩次頁面重新渲染,總計 20 次。要解決這個問題就如上面所言的,減少 DOM 操作,將清單項目在添加好文本節點以後再添加。下面的代碼就可以與上述的程式碼完成同樣的任務。var oUl = document.getElementById("ulItem");var oTemp = document.createDocumentFragment();for (var i = 0; i < 10; i++) { var oLi = document.createElement("li"); oLi.appendChild(document.createTextNode("Item " + i); oTemp.appendChild(oLi);}oUl.appendChild(oTemp);遵循標準的 DOM說點書中沒有的(照本宣科完畢),Javascript 其實在尋找節點(Node)也會花上一段時間。對 Web 標準友好的 (x)html 文檔相對雜亂文章的頁面來說,Javascript 執行速度兩者也會有所差別。瀏覽器處理頁面有模式之分,這也許也是為什麼要編寫遵循 Web 標準的頁面的原因之一。具體資訊可以參考這裡和一些言論。緩衝 AjaxAjax 雖然提供了頁面非同步請求調用,但別忘記了它還是訪問伺服器的。Javascript 作為驅動層本身可以作為緩衝使用,雖然在頁面重新載入後就會被釋放,但對於伺服器而言這是一個好的訊息。結束語不知不覺就寫了那麼多,很多東西都是書上照本宣科的。《Javascript 進階程式設計》的確是一本不可多得的好書,建議大家有機會都可以去看看。這本書不貴,59 RMB(可能在別的地方還有打折),對於煙民而言也就一條雙精確度紅喜,不過它可比香煙所能帶來的快感多得多。