高性能JavaScript讀書筆記
零、組織結構
根據引言,作者將全書劃分為四個部分:
一、頁面加載js的最佳方式(開發前準備)
二、改善js代碼的編程技巧(開發中)
三、構建與部署(發布)
四、發布後性能檢測與問題追蹤(線上問題優化)
這樣的組織結構也符合我們的開發習慣,首先進入第一部分。
一、JavaScript加載
起因:script腳本的加載會阻塞瀏覽器渲染頁面和處理用戶交互,如果加載的script腳本太多太大,就會長時間阻塞,造成頁面假死。
解決方案:
1.腳本位置
放在底部。
放在底部可以保證頁面主體結構已經基本加載完成展示給用戶,才去加載script腳本。這條準則也有特例,如果是一些公司的埋點腳本,可能要求必須放在head裏,以確保能上報頁面加載時長。
2.組織腳本
減少script數量,合並腳本。
不論是使用concat打包工具合並,還是cdn提供的combo,都能達到該目的。
3.無阻塞腳本
在頁面加載完成之後再去觸發加載js,這樣可以防止script加載阻塞頁面。
原生defer
script標簽自帶defer屬性,可以達到這個目的。捎帶一提,async屬性可以觸發異步加載,合理利用了寬帶。
動態腳本
或者使用動態腳本元素,通過js文件動態創建script標簽,文件加載完成之後會立刻執行。
這個技術的重點在於不論在何時啟動下載,文件的下載和執行過程都不會阻塞頁面其他進程。
XMLHttpRequest腳本註入
通過XHR加載腳本,由於不是在script標簽中加載,所以可以控制script標簽中的代碼執行時間。在需要的時候再去把script標簽添加到頁面上。
這也是jsonp的原理。
作者推薦的無阻塞模式
先加載很少量的jsLoader代碼,之後通過loader來加載剩余的代碼。
function loadScript(url, callback) {
var script = document.createElement(‘script‘);
script.type = ‘text/javascript‘;
if (script.readyState) { // IE
script.onreadystatechange = function () {
if (script.readyState == ‘loaded‘ || script.readyState == ‘complete‘) {
script.onreadystatechange = null;
callback();
}
}
} else { // 其他瀏覽器
script.onload = function () {
callback();
}
}
script.src = url;
document.getElementsByTagName(‘head‘)[0].appendChild(script);
}
loadScript(‘rest.js‘, function () {
Rest.init()
});
YUI3、LazyLoad、LABjs
作者介紹了上述3個類庫中lazyload的使用方式,其實原理就是上述所說,在體驗上各有不同。關鍵就是loader中監聽script的onload事件,決定script的執行時機。
二、高性能編程技巧
這個部分從2到8章分別介紹不同的提高性能的技巧。
1.數據存取
- 數據的存取有四種方式,字面量、變量、數據項、對象。字面量和變量快於數組項和對象。
- 局部變量位於作用域鏈的開始,查找速度最快,所處的位置越深,查找越慢,全局變量查找最慢。
- with可以影響作用域鏈,try-catch語句中的catch也會影響。慎重使用。
- 如果當前對象沒有某個屬性或者方法,就會去遍歷原型鏈上的屬性和方法,也會造成性能損耗。
嵌套對象層數越多,也會減慢速度。例如,location.href要快於window.location.href。
解決方法:
大部分的性能損耗都是與對象查找相關,所以建議緩存對象,減少查找次數。2.DOM編程
- 由於DOM渲染引擎和JS引擎一般是分離實現的,要讓兩個相互獨立的模塊建立通訊,就會產生消耗。所以,我們需要減少DOM操作,已經獲取的DOM節點,緩存下來,防止多次重復操作。
其次,要註意重繪和重排,對DOM的哪些操作會引起重繪和重排,減少這方面的操作。
最後,需要註意DOM事件處理用戶交互。使用innerHTML和原生的document.createElement()類似的方法,速度相差不大。在最新版的webkit內核瀏覽器(chrome和safari)以外,innerHTML會更快,而且相較而言,代碼量也少很多。
HTML集合是包含了DOM節點引用的類數組對象,例如document.getElementByName()這樣的方法,需要註意的是HTML集合一直與文檔保持著連接,每次需要新信息的時候,都會重新查詢。
在查找DOM元素時,盡可能使用原生方法,如最新支持比較完善的querySelectorAll()。
- 在頁面布局和幾何屬性改變時就需要“重排”,例如:
- 添加或者刪除可見的DOM元素
- 元素位置改變
- 元素尺寸改變(包括:外邊距、內邊距、邊框厚度、寬度、高度等屬性改變)。
- 內容改變,例如:文本改變或者圖片被另一個尺寸的圖片替代
- 頁面渲染器初始化
- 瀏覽器窗口尺寸改變
為了減少重排和重繪,批量修改樣式時,“離線”操作DOM樹,使用緩存,並減少訪問布局信息的次數。在動畫中使用絕對定位,使用拖放代理。
使用事件委托來減少事件處理器的數量。
3.算法和流程控制
- 減少叠代的工作量,比如使用局部變量緩存數組的長度,減少查詢次數。
- 減少叠代次數,使用Duff‘s Device。
- 盡可能不用forin,forin要比for、while、do-while要慢。
- switch比if-else要快,但要綜合考慮代碼可讀性。
- 判斷條件較多時,查找表要比if-else和switch更快。
- 遞歸容易造成棧溢出,控制遞歸的循環次數。
4.字符串和正則表達式
- 連接巨大的字符串時,數組項合並在IE7或更早之前版本是最優方法。
- 如果在現代瀏覽器中,推薦使用簡單的+和+=操作符代替,避免不必要的中間字符串。
- 回溯是正則的基本組成,也會帶來性能問題。
- 回溯失控發生在正則表達式本應快速匹配的地方,因為某些特殊的字符串匹配動作導致運行緩慢甚至瀏覽器崩潰。為了避免這種情況,應該使相鄰的字元互斥,避免嵌套量詞對同一字符串的相同部分多次匹配,通過利用預查的原子組去除不必要的回溯。
5.快速響應的用戶界面
- 任何JavaScript任務不應該超過100ms,否則用戶體驗會變差。
- JavaScript運行期間,瀏覽器響應用戶交互的行為存在差異。
- 定時器可用來安排代碼延遲執行,但不一定絕對精確。
- web worker允許程序員在UI線程外執行JavaScript代碼,從而避免鎖定UI。
6.AJAX
- 減少請求數
- 縮短頁面加載時間,頁面主要內容加載完成之後,用Ajax獲取次要內容
- 代碼錯誤盡可能對用戶屏蔽
7.最佳實踐
- 避免使用eval和Function構造器帶來雙重求值帶來的性能損耗。
- 使用直接量創建對象和數組,直接量更快
- 避免重復工作,需要檢測瀏覽器時,可使用延遲加載或者條件預加載。
- 進行數學計算時,考慮使用位運算。
- 盡量使用原生方法
三、構建與部署
- 合並JavaScript文件以減少HTTP請求數
- 使用YUI Compressor壓縮JavaScript文件(事實上,如今nodejs下的打包工具更加好用)
- 在服務端壓縮JavaScript文件(Gzip編碼)
- 通過正確設置HTTP響應頭來緩存JavaScript文件,通過向文件名加時間戳來避免緩存問題
- 使用CDN
四、發布後性能檢測與問題追蹤
- 使用網絡分析工具找出加載腳本和頁面中其他資源的瓶頸。
- 把腳本延遲加載可以加快頁面渲染速度,帶來更好的用戶體驗
- 使用性能分析工具找出腳本運行速度慢的地方,檢查每個函數消耗的時間,以及函數被調用的次數,通過調用棧自身的一些線索來找出需要集中精力優化的地方
一些工具: - YUI Profile
- Firebug
- Page Speed
- Fiddler
- YSlow
- dynaTrace Ajax Edition
事實上,這些工具的功能在新版的chrome開發工具都有了,所以我建議好好學習chrome開發者工具。
感謝閱讀。
高性能JavaScript讀書筆記