做了這么久的前端工程師,總被朋友問到怎么寫出高性能的javascript,那么我今天就來簡單總結下,其實js本身是沒有什么性能問題的,所謂的內存泄露,也主要針對於IE6,IE7,而IE7的內存泄露問題也並不嚴重,這里不討論瀏覽器造成的內存泄露問題,我們只討論,養成什么樣的書寫習慣能夠寫出,高效率高性能的js。
在這里我總結了三個書寫js的習慣,然后分別針對執行效率,內存問題,安全等各個方面綜合分析要養成這三個習慣的原因。
1.盡量使用局部變量:
局部變量的創建和訪問都是特別廉價快捷的,而使用全局變量的話,js對全局變量的調用實際上是對GLOBAL對象的查找引用,性能低下。 而局部變量則是直接創建於當前作用域,不需要有查找引用的過程。不單單是當前作用域,包括閉包里的局部變量訪問,都是很快的。不過,這個過程帶來的優化是需要龐大的js程序長時間的運行才能體現出的。
下面通過細化js解析過程來理解下:
一個變量具體是局部變量還是全局變量,局部變量的話是在哪層閉包里,是第幾個變量,在編譯的時候就已經確定了,重復的var聲明並不會影響js的執行效率,js執行時,如果是局部變量則會直接在向內存地址里取用,而全局變量則是對象訪問,顯然局部變量在效率上要優化的多。
但是,不是任何時候局部變量都是最優化的。
例如,當一個變量是一個表達式或者一個查找dom節點的過程等時,變量的取用中就帶有了查找和計算過程,這樣如果計算次數比較多,用全局變量則可以一次性的固化結果,而使用局部變量反而需要多次計算查找,而影響了效率了。
總結:
當然,以上的數據僅僅只對js代碼的執行效率而言,在具體應用中,我仍然推薦大家盡量使用局部變量。因為,js的一個重要的編程理念就是不要污染全局對象,全局對象可能被不同的模塊(甚至頁面上的廣告)訪問,在上面存放數據、函數會有不可預期的后果。所以,我們要靈活使用全局對象和局部變量。
2.及時釋放回調函數,解除事件綁定。
上面一個習慣,主要針對於執行效率問題,而這個習慣則是針對內存泄露的問題了。除了古老瀏覽器本身的bug造成的內存泄露外,錯誤的書寫js代碼也會引起內存泄露的問題。
首先,有如下代碼解釋下這句話的意思:
1 img.onload = function () { 2 img.onload = img.onerror = null; 3 } 4 $(...).on('keyup', function(e){ 5 $().off(e); 6 })
那這么做有什么好處呢,那么我們看下下面的情況:
3.及時清除引用。
這個習慣也是針對於內存泄露問題,我們先看看以下代碼:
1 <!doctype html> 2 <html> 3 <head> 4 <title>Memory leak demo</title> 5 </head> 6 <body> 7 <a href="javascript:;">Click me</a> 8 </body> 9 <script src="jquery-1.8.0.js"></script><script> 10 $('a').click(function callback(e) { 11 $(this).remove(); 12 $('<a href="javascript:;">Click me</a>').appendTo(document.body).click(function(e2){ 13 e2.leak = e; 14 callback.call(this, e2); 15 }); 16 17 var current = e, n = 0; 18 while(current.leak){ 19 current = current.leak; 20 n++; 21 } 22 console.log('leaked: ' + n); 23 //e = null; 24 }); 25 </script> 26 </html>
這是段代碼在不停的替換頁面中的a標簽,並且為新的a標簽綁定clcik事件,在事件的回調函數中,引用了上一個事件的e,導致了原本的a標簽即使已經被remove也無法被釋放,仍然保存在內存里。隨着用戶click次數的增多,內存泄露問題也會越來越嚴重。
所以這個時候,我們就要記得及時清除引用,這段代碼的內存泄露問題是由於事件對象e引起的,在23行,去掉注釋后,e = null 釋放了內層函數對外層函數的變量e指向的對象的引用,使得e以及它引用的dom節點能被釋放,內存泄露問題也就得以解決了。
當然,想寫出高性能的js,還有很多可以仔細研究的地方,而上面的三個習慣是可以在日常的編碼中養成的,也沒有什么替換成本。還有很多良好的習慣,比如減少函數引用,盡量少使用閉包,等,想做到這些方式可能需要較高的替換成本,如果有機會以后再與大家分享吧~。
