JavaScript 常常會觸發視覺變化。有時是直接經過樣式操做,有時是會產生視覺變化的計算,例如搜索數據或將其排序。時機不當或長時間運行的 JavaScript 多是致使性能問題的常見緣由。您應當設法儘量減小其影響。javascript
JavaScript 性能分析能夠說是一門藝術,由於您編寫的 JavaScript 代碼與實際執行的代碼徹底不像。現代瀏覽器使用 JIT 編譯器和各類各樣的優化和技巧來嘗試爲您實現儘量快的執行,這極大地改變了代碼的動態。前端
注:若是您真的想了解 JIT 的實例,應當查看 Vyacheslav Egorov 提供的 IRHydra2。它顯示了當 Chrome 的 JavaScript 引擎 V8 對 JavaScript 代碼進行優化時,JavaScript 代碼的中間狀態。java
儘管如此,您確定仍是能夠作一些事情來幫助您的應用很好地執行 JavaScript。但 Javascript 性能優化毫不是一種書面的技術,借鑑別人的意見和本身平時項目的總結給出如下幾條建議:react
當一個變量被引用的時候,JavaScript將在做用域鏈中的不一樣成員中查找這個變量。做 用域鏈指的是當前做用於下可用變量的集合,它在各類主流瀏覽器中至少包含兩個部分:局部變量的集合和全局變量的集合。程序員
簡單地說,若是JavaScript引擎在做用域鏈中搜索的深度越大,那麼操做也就會消耗更多的時間。引擎首先從 this 開始查找局部變量,而後是函數參數、本地定義的變量,最後遍歷全部的全局變量。數組
由於局部變量在這條鏈的起端,因此查找局部變量老是比查找全局變量要塊。因此當你想要不止一次地使用一個全局變量的時候,你應該將它定義成局部變量,就像這樣:瀏覽器
var dom1 = document.getElementById('id1'), dom2 = document.getElementById('id2');
改寫成性能優化
var document = document, demo1 = document.getElementById('id1'), demo2 = document.getElementById('id2');
雖然你可能還不知道「閉包」,但你可能在不經意間常用這項技術。閉包基本上被認爲是JavaScript中的new,當咱們定義一個即時函數的時候,咱們就使用了閉包,好比:閉包
document.getElementById('dom').onclick = function(ev) { };
閉包的問題在於:根據定義,在它們的做用域鏈中至少有三個對象:閉包變量、局部變量和全局變量。這些額外的對象將會致使第1和第2個建議中提到的性能問題。dom
閉包對於提升代碼可讀性等方面仍是很是有用的,只是不要濫用它們(尤爲在循環中)。
談到JavaScript的數據,通常來講有4種訪問方式:數值、變量、對象屬性和數組元素。在考慮優化時,數值和變量的性能差很少,而且速度顯著優於對象屬性和數組元素。
所以當你屢次引用一個對象屬性或者數組元素的時候,你能夠經過定義一個變量來得到性能提高。(這一條在讀、寫數據時都有效)
雖然這條規則在絕大多數狀況下是正確的,可是Firefox在優化數組索引上作了一些有意思的工做,可以讓它的實際性能優於變量。可是考慮到數組元素在其餘瀏覽器上的性能弊端,仍是應該儘可能避免數組查找,除非你真的只針對於火狐瀏覽器的性能而進行開發。
不使用DOM是JavaScript優化中另外一個很大的話題。經典的例子是添加一系列的列表項:若是你把每一個列表項分別加到DOM中,確定會比一次性加入全部列表項到DOM中要慢。這是由於DOM操做開銷很大。
Zakas對這個進行了細緻的講解,解釋了因爲迴流(reflow)的存在,DOM操做是很是消耗資源的。迴流一般被理解爲瀏覽器從新選渲染DOM樹的處理過程。好比說,若是你用JavaScript語句改變了一個div的寬度,瀏覽器須要重繪頁面來適應變化。
任什麼時候候只要有元素被添加到DOM樹或者從DOM樹移除,都會引起迴流。使用一個很是方便的JavaScript對象能夠解決這個問題——documentFragment,我並無使用過,可是在Steve Souders也表示贊成這種作法以後我感受更加確定了。
DocumentFragment 基本上是一種瀏覽器以非可視方式實現的相似文檔的片斷,非可視化的表現形式帶來了不少優勢,最主要的是你能夠在 documentFragment 中添加任何結點而不會引發瀏覽器迴流。
另外,程序員應該避免在數組中挖得太深,由於進入的層數越多,操做速度就越慢。
簡單地說,在嵌套不少層的數組中操做很慢是由於數組元素的查找速度很慢。試想若是操做嵌套三層的數組元素,就要執行三次數組元素查找,而不是一次。
所以若是你不斷地引用 foo.bar, 你能夠經過定義 var bar = foo.bar 來提升性能。
若是針對的是不斷運行的代碼,不該該使用setTimeout,而應該是用setInterval,由於setTimeout每一次都會初始化一個定時器,而setInterval只會在開始的時候初始化一個定時器。
var timeoutTimes = 0; function timeout() { timeoutTimes++; if (timeoutTimes < 10) { setTimeout(timeout, 10); } } timeout(); //能夠替換爲: var intervalTimes = 0; function interval() { intervalTimes++; if (intervalTimes >= 10) { clearInterval(interv); } } var interv = setInterval(interval, 10);
對於小型react前端應用,最好的優化就是不優化由於React自己就是經過比較虛擬DOM的差別,從而對真實DOM進行最小化操做,小型React應用的虛擬DOM結構簡單,虛擬DOM比較的耗時能夠忽略不計。而對於複雜的前端項目,咱們所指的渲染性能優化,其實是指,在不須要更新DOM時,如何避免虛擬DOM的比較。
react組件的生命週期
工欲善其事,必先利其器。理解react的組件的生命週期是優化其渲染性能的必備前提。咱們能夠將react組件的生命週期分爲3個大循環:掛載到DOM、更新DOM、從DOM中卸載。React對三個大循環中每一步都暴露出鉤子函數,使得咱們能夠細粒度地控制組件的生命週期。
掛載到DOM
組件首次插入到DOM時,會經歷從屬性和狀態初始化到DOM渲染等基本流程,能夠經過下圖所示
必須注意的是,掛載到DOM流程在組件的整個生命週期只有一次,也就是組件第一次插入DOM文檔流時。在掛載到DOM流程中的每一步也有相應的限制:
更新DOM
組件掛載到DOM後,一旦其props和state有更新,就會進入更新DOM流程。一樣咱們也能夠經過一張圖清晰的描述該流程的各個步驟:
//getDefaultProps()和getInitialState()中不能獲取和設置組件的state。 //render()方法中不能設置組件的state。
componentWillReceiveProps()提供了該流程中更新state的最後時機,後續的其餘函數都不能再更新組件的state了。咱們尤爲須要注意的是shouldComponentUpdate函數,它的結果直接影響該組件是否是須要進行虛擬DOM比較,咱們對組件渲染性能優化的基本思路就是:在非必要的時候將shouldComponentUpdate返回值設置爲false,從而終止更新DOM流程中的後續步驟。
從DOM中卸載
從DOM中卸載的流程比較簡單,React只暴漏出componentWillUnmount,該函數使得咱們能夠在DOM卸載的最後時機對其進行干預。
二、性能分析
合理的使用shouldComponentUpdate()能夠在很大程序上優化應用。但在實際狀況下,應用每每在沙箱或是開發環境中運行的很是快,但生產環境則表現的不盡人意。這時,咱們須要對應用進行性能分析,而後再有針對性的在shouldComponentUpdate()中進行優化。 React 提供了性能分析插件React.addons.Perf,它讓咱們能夠在須要檢測的代碼起始位置分別添加Perf.start()和Perf.stop(),並能夠經過Perf.printInclusive()方法打印花費時間,而後咱們能夠結合數據作進一步的分析。 React.addons.Perf插件的詳細用法,能夠查看官方文檔。
三、 藉助react Key標識組件
key屬性在組件類以外提供了另外一種方式的組件標識。經過key標識咱們能夠組件如:順序改變、沒必要要的子組件更新等狀況下,告訴React 避免沒必要要的渲染而避免性能的浪費。
如,對於如一個基於排序的組件渲染
var items = sortBy(this.state.sortingAlgorithm, this.props.items); return items.map(function(item){ return <img src={item.src} /> });
當順序發生改變時,React 會對元素進行diff操做,並改img的src屬性。顯示,這樣的操做效率是很是低的。這時,咱們能夠爲組件添加一個key屬性以惟一的標識組件:
return <img src={item.src} key={item.id} />