Javascript 效能最佳化絕不是一種書面的技術,Nicholas 的技術演進列出了10個建議,幫助你寫出高效的JS 程式碼
1. 定義局部變數
當一個變數被引用的時候,JavaScript會在作用域鏈中的不同成員中尋找這個變數。作用域鏈指的是目前作用於下可用變數的集合,它在各種主流瀏覽器中至少包含兩個部分:局部變數的集合和全域變數的集合。
簡單來說,如果JavaScript引擎在作用域鏈中搜尋的深度越大,那麼操作就會消耗更多的時間。引擎先從 this 開始尋找局部變量,然後是函數參數、本地定義的變量,最後遍歷所有的全域變數。
因為局部變數在這條鏈的起端,所以尋找局部變數總是比尋找全域變數要塊。所以當你想要不只一次地使用一個全域變數的時候,你應該將它定義成局部變量,就像這樣:
var blah = document.getElementById('myID'), blah2 = document.getElementById('myID2');
改寫成
var doc = document, blah = doc.getElementById('myID'), blah2 = doc.getElementById('myID2');
2. 不要使用with() 語句
這是因為with() 語句將會在作用域鏈的開始加入額外的變數。額外的變數意味著,當任何變數需要被存取的時候,JavaScript引擎都需要先掃描with()語句產生的變量,然後才是局部變量,最後是全域變數。
So with() essentially gives local variables all the performance drawbacks of global ones, and in turn derails Javascript optimization. 因此with()語句同時給局部變數和全域變數的效能帶來負面影響,最終使我們優化JavaScriptScript性能的計劃破產。
3. 小心使用閉包
#雖然你可能還不知道“閉包”,但你可能在不經意間經常使用這項技術。閉包基本上被認為是JavaScript中的new,當我們定義一個即時函數的時候,我們就使用了閉包,例如:
document.getElementById('foo').onclick = function(ev) { };
閉包的問題在於:根據定義,在它們的作用域鏈中至少有三個物件:閉包變數、局部變數和全域變數。這些額外的物件將會導致第1和第2個建議中提到的效能問題。
但我認為Nicholas並不是要我們因噎廢食,閉包對於提高程式碼可讀性等方面還是非常有用的,只是不要濫用它們(尤其在循環中)。
4. 物件屬性和陣列元素的速度都比變數慢
談到JavaScript的數據,一般來說有4種存取方式:數值、變數、物件屬性和數組元素。在考慮最佳化時,數值和變數的效能差不多,且速度顯著優於物件屬性和陣列元素。
因此當你多次引用一個物件屬性或陣列元素的時候,你可以透過定義一個變數來獲得效能提升。 (這一條在讀取、寫入資料時都有效)
雖然這條規則在絕大多數情況下是正確的,但是Firefox在優化數組索引上做了一些有意思的工作,能夠讓它的實際性能優於變數。但考慮到數組元素在其他瀏覽器上的效能弊端,還是應該盡量避免數組查找,除非你真的只針對於火狐瀏覽器的效能而進行開發。
5. 不要在陣列中挖得太深
另外,程式設計師應該避免在陣列中挖得太深,因為進入的層數越多,操作速度就越慢。
簡單地說,在嵌套很多層的數組中操作很慢是因為數組元素的查找速度很慢。試想如果操作嵌套三層的陣列元素,就要執行三次組元素查找,而不是一次。
因此如果你不斷地引用 foo.bar, 你可以透過定義 var bar = foo.bar 來提高效能。
6. 避免 for-in 迴圈(和基於函數的迭代)
這是另一個非常教條的建議:不要使用for-in迴圈。
這背後的邏輯非常直接:要遍歷一個集合內的元素,你可以使用諸如for循環、或者do-while循環來替代for-in循環,for-in循環不僅僅可能需要遍歷額外的數組項,還需要更多的時間。
為了遍歷這些元素,JavaScript需要為每個元素建立一個函數,而這個基於函數的迭代帶來了一系列效能問題:額外的函數引入了函數物件被建立和銷毀的上下文,將會在作用域鏈的頂端增加額外的元素。
7. 在迴圈時將控制條件和控制變數合併起來
提到效能,在迴圈中需要避免的工作一直是個熱門話題,因為迴圈會被重複執行很多次。所以如果有效能優化的需求,先對循環開刀有可能會得到最明顯的效能提升。
一種最佳化迴圈的方法是在定義迴圈的時候,將控制條件和控制變數合併起來,以下是一個沒有將他們合併起來的例子:
for ( var x = 0; x < 10; x++ ) { };
當我們要添加什麼東西到這個循環之前,我們發現有幾個操作在每次迭代都會出現。 JavaScript引擎需要:
#1:检查 x 是否存在
#2:检查 x 是否小于 0 (译者注:我猜这里是作者的笔误)
#3:使 x 增加 1
然而如果你只是迭代元素中的一些元素,那么你可以使用while循环进行轮转来替代上面这种操作:
var x = 9; do { } while( x-- );
如果你想更深入地了解循环的性能,Zakas提供了一种高级的循环优化技巧,使用异步进行循环(碉堡了!)
8. 为HTML集合对象定义数组
JavaScript使用了大量的HTML集合对象,比如 document.forms,document.images 等等。通常他们被诸如 getElementsByTagName、getElementByClassName 等方法调用。
由于大量的DOM selection操作,HTML集合对象相当的慢,而且还会带来很多额外的问题。正如DOM标准中所定义的那样:“HTML集合是一个虚拟存在,意味着当底层文档被改变时,它们将自动更新。”这太可怕了!
尽管集合对象看起来跟数组很像,他们在某些地方却区别很大,比如对于特定查询的结果。当对象被访问进行读写时,查询需要重新执行来更新所有与对象相关的组分,比如 length。
HTML集合对象也非常的慢,Nicholas说好像在看球的时候对一个小动作进行60倍速慢放。另外,集合对象也有可能造成死循环,比如下面的例子:
var ps = document.getElementsByTagName('p'); for (var i=0; i < ps.length; i++ ) { var p = document.createElement("p"); document.appendChild(p); }
这段代码造成了死循环,因为 ps 表示一个实时的HTML集合,并不是你所期望的数组。这种实时的集合在添加 e388a4556c0f65e1904146cc1a846bee 标签时被更新,所以i < p.length 永远都不会结束。
解决这个问题的方法是将这些元素定义成数组,相比只设置 var ps = document.getElementsByTagName(‘p') 稍微有点麻烦,下面是Zakas提供的强制使用数组的代码:
function array(items) { try { return Array.prototype.concat.call(items); } catch (ex) { var i = 0, len = items.length, result = Array(len); while (i < len) { result[i] = items[i]; i++; } return result; } } var ps = array( document.getElementsByTagName('p') ); for (var i=0l i < ps.length; i++ ) { var p = document.createElement("p"); document.appendChild(p); }
9. 不要碰DOM!
不使用DOM是JavaScript优化中另一个很大的话题。经典的例子是添加一系列的列表项:如果你把每个列表项分别加到DOM中,肯定会比一次性加入所有列表项到DOM中要慢。这是因为DOM操作开销很大。
Zakas对这个进行了细致的讲解,解释了由于回流(reflow)的存在,DOM操作是非常消耗资源的。回流通常被理解为浏览器重新选渲染DOM树的处理过程。比如说,如果你用JavaScript语句改变了一个p的宽度,浏览器需要重绘页面来适应变化。
任何时候只要有元素被添加到DOM树或者从DOM树移除,都会引发回流。使用一个非常方便的JavaScript对象可以解决这个问题——documentFragment,我并没有使用过,但是在Steve Souders也表示同意这种做法之后我感觉更加肯定了。
DocumentFragment 基本上是一种浏览器以非可视方式实现的类似文档的片段,非可视化的表现形式带来了很多优点,最主要的是你可以在 documentFragment 中添加任何结点而不会引起浏览器回流。
10. 修改CSS类,而不是样式
你也许听说过:修改CSS类必直接修改样式会更高效。这归结于回流带来的另一个问题:当布局样式发生改变时,会引发回流。
布局样式意味着任何影响改变布局的变化都会强制引起浏览器回流。比如宽度、高度、字号、浮动等。
但是别误会我的意思,CSS类并不会避免回流,但是可以将它的影响最小化。相比每次修改样式都会引起回流,使用CSS类一次修改多个样式,只需要承担一次回流带来的消耗。
因此在修改多个布局样式的时候,使用CSS类来优化性能是明智的选择。另外如果你需要在运行时定义很多歌CSS类,在DOM上添加样式结点也是不错的选择。
以上是如何提升JavaScript Web效能的技巧總結的詳細內容。更多資訊請關注PHP中文網其他相關文章!