標籤:
第一章:載入和執行
1.1指令碼位置
將js指令碼放在body底部
1.2組織指令碼
檔案合并,減少http請求(打包工具)
1.3無阻塞的指令碼
js傾向於阻止瀏覽器的某些處理過程,如http請求和使用者介面更新,這是所有開發人員面臨的最顯著的效能問題。
儘管下載單個較大的js檔案只產生一次http請求,卻會死結瀏覽器一大段時間。為避免這種情況,你需要向頁面中逐步載入js檔案,這樣做在某種程度上來說不會阻塞瀏覽器。
無阻塞指令碼的秘訣在於,在頁面載入完成後才載入js代碼。用專業術語來說,這意味著在window對象的load事件觸發後再下載指令碼。有很多方式可以做到這一點。
1.3.1延遲的指令碼
HTML4為<script>標籤添加了defer屬性(部分瀏覽器支援),該屬性指明本元素所含指令碼不會修改DOM,因此代碼能夠安全地順延強制。
1.3.2動態指令碼元素
動態載入js檔案,無論在何時啟動下載,檔案的下載和執行過程不會阻塞頁面其他進程。
使用動態指令碼節點下載檔案時,返回的代碼通常會立即執行。當指令碼“自執行”時,這種機制運行正常。但是當代碼只包含供頁面其他指令碼調用的介面時,你必須跟蹤並確保指令碼下載完成並準備就緒。可以通過偵聽此事件來獲得指令碼載入完成時的狀態:
function loadScript(url ,callback){ var script = documment.creatElement("script") script.type = "text/javascript"; if(script.readystatechange = function(){//IE script.onreadystatechange = function(){ if(script.readystate == "load‘ || script.readystate == "complete"){ script.onraedystatechange = null; callback(); } }; }else{//其它瀏覽器 script.onload = function(){ callback(); }; }
script.src = url; document.getElementsByTagName("head")[0].appendChild(script);}
如果需要動態載入多個js檔案,則需確保檔案載入的順序。在所有的主流瀏覽器中,只有firefox和opera能夠按照開發人員指定的順序執行,其他瀏覽器會按照從服務端還回的順序下載和執行代碼。你可以將下載操作串聯起來以確保下載順序,如
loadScript("file1.js",function(){ loadScript("file2.js,function(){ loadScript("file3.js,function(){ loadScript("file4.js,function(){ alert("All files are loaded!") }); }); }); });
儘管方案可行,但如果需要下載的檔案較多,這個方案會帶來管理上的麻煩。
如果多個檔案下載順序很重要,更好的做法是把它們按照正確順序寫成一個檔案(由於這個過程是非同步,因此大一點的檔案不會有影響)。
1.3.3XMLHttpRequest指令碼注入
此技術會先建立一個XHR對象,然後用它下載js檔案,最後通過動態建立<script>元素將代碼注入頁面中。
1.3.4推薦的無阻塞模式
先添加動態載入所需的代碼,然後載入初始化頁面所需的剩下的代碼。
第二章 資料訪問
js的四種基本資料存取位置
直接量
直接量只代表自身,不儲存在特定的位置。有字串、數字、布爾值、對象、數組、函數、Regex以及特殊的null和undefined。
變數
開發人員用關鍵字var定義的資料儲存單元。
數組元素
儲存在js數組對象內部,以數字作為索引。
對象成員
儲存在js對像內部,以字串作為索引。
大多數情況下,從一個直接量和一個局部變數中存取資料比訪問數組元素和對象成員的代價小些。
2.1管理範圍
2.1.1範圍鏈和標識符解析
function對象的內部屬性[[Scope]]包含了一個函數被建立的範圍中對像的集合,這個集合被稱為函數的範圍鏈。
2.1.2標識符解析的效能
標識符解析是有代價的,事實上沒有哪種電腦操作可以不產生效能開銷。在運行期內容相關的範圍中,一個標識符所在的位置越深,它的讀寫速度也就越慢。
一個好的經驗法則:如果某個跨範圍的值在函數中被引用一次以上,那麼就把它儲存到局部變數中。
2.1.3改變範圍鏈
一般來說,一個運行期內容相關的範圍鏈是不會改變的。但是,有兩個語句可以在執行時臨時改變範圍鏈——with和catch子句。
with包含了參數指定對象的所有屬性,這個對象被推入所用範圍鏈的頭部,這意味這所有局部變數第二個範圍鏈對象中,因此訪問的代價更高。
把一個異常對象推入一個可變對象共置於範圍的頭部。一旦catch子句執行完畢,範圍鏈就會返回到之前的狀態。
2.1.4動態範圍
2.1.5閉包、範圍和記憶體
因為閉包的[[Scope]]屬性包含了與運行上下文範圍鏈相同的對象的引用,因此會有一項副作用。通常來說,函數的使用中的物件,會隨同運行期上下文一同銷毀。但引入閉包時,由於引用仍然存在於閉包的[[Scope]]屬性中,因此啟用物件無法被銷毀。這意味著指令碼中的閉包與非閉包相比,需要更多的記憶體開銷。在大幸Web應用中,這可能是個問題,尤其在IE瀏覽器中需要關注。由於IE使用非原生js對象來實現DOM對象,因此閉包會導致記憶體流失。
2.2對象成員
2.2.1原型
js中的對象是基於原型的。原型是其他對象的基礎,定義並實現了一個新對象必須包含的成員列表。這一概念完全不同於傳統物件導向程式設計語言中“類”的概念,“類”定義了建立新對象的過程。而原型對象為所有對象執行個體共用,因此這些執行個體也共用了原型對象的成員。
對象可以有兩種成員類型:執行個體成員和原型成員。
可以用hasOwnProperty()方法來判斷對象是否包含特定的執行個體成員,用in操作符來確定對象是否包含特定的屬性。
2.2.2原型鏈
對象的原型決定了執行個體的類型。預設情況下,所有對象都是對象(Object)的執行個體,並繼承了所有基本方法。
2.2.3嵌套成員
2.2.4緩衝對象成員值
由於所有類似的效能問題都與對象成員有關,因此應該儘可能避免使用它們。更確切地說,應當小心,只有在必要時使用對象成員。
如:
function hasEitherClass(element,className1,className2){ return element.className == className1 || element.className == className2;}
換成
function hasEitherClass(element,className1,className2){ var currentClassName = element.className; renturn currentClassName == className1 || currentClassName ==className2;}
js的命名空間是導致頻繁訪問嵌套屬性的起因之一,不要再同一個函數裡多次尋找同一個對象成員,除非它的值改變了。
第3章 DOM編程
用指令碼進行DOM操作的代價很昂貴,它是富Web應用中最常見的效能瓶頸。
三類
1.訪問和修改DOM元素
2.修改DOM元素的樣式會導致重繪(repaint)和重排(reflow)
3.通過DOM事件處理與使用者的互動
3.1瀏覽器中的DOM
DOM與js是兩個相互獨立的功能,它們通過介面彼此串連,就會產生消耗。
3.2DOM訪問與修改
修改元素會更為昂貴,因為它會導致瀏覽器重新計算頁面的幾何變化。
3.2.1innerHTML對比DOM方法
最終選擇哪種方式取決於你的使用者經常使用的瀏覽器,以及編碼習慣。
3.2.2節點複製
3.2.3HTML集合
HTML集合是包含了DOM節點引用的類數組對象。
讀取一個集合的length比讀取普通數組的length要慢很多,因為每次都要重新查詢。
高效能JS(讀書劄記)