寫在前面
好的書,可能你第一遍并不能領會里面的精魂,當再次細細品評的時候,發現領悟的又是一層新的含義
(這段時間,工作上也不會像從前一樣做起來毫不費力,開始有了新的挑戰,現在的老大讓我既佩服又嫉妒,但真的是打心底里仰慕,希望自己有朝一日能過到他那個高度)
既然現在還達不到那個層次,就好好堆磚吧,當磚堆到一定高度也自然會上一個小臺階。
腳本位置
腳本會阻塞頁面渲染,直到它們全部下載并執行完成,頁面才會繼續渲染。頁面只有加載并執行完前面一個script外部文件才會去加載下面一個script標簽。
在IE8、FireFox3.5、Safari4、和Chrome2才開始允許并行下載JavaScript文件,也就是說從這個時候開始script標簽在下載外部資源時不會阻塞其它script標簽,但是javascript下載的過程仍然會阻塞其它資源的下載(比如圖片),盡管腳本的下載過程不會互相影響,但頁面仍然必須等待所有的javascript代碼下載并執行完成才能繼續
這也就是為什么推薦將所有的script標簽盡可能放到body標簽的底部,以盡量減少對整個頁面下載的影響
組織腳本
由于每個script標簽初始下載時都會阻塞頁面渲染,所以減少頁面包含的script標簽數量能改善頁面性能(這不僅僅針對外鏈腳本,內嵌腳本的數量也同樣限制)
把一段內嵌腳本放在引用外鏈樣式表的link標簽之后會導致頁面阻塞去等待樣式表的下載,瀏覽器這樣做是為了確保內嵌腳本在執行時能獲得最精確的樣式信息,因此,建議永遠不要把內嵌腳本緊跟在link標簽后面
把多個文件合并為一個,可以減少性能消耗
無阻塞腳本
無阻塞腳本也就是在頁面加載完成后才加載javaScript代碼,有多種方式來實現
1、script標簽有一個擴展屬性defer,defer屬性指明本元素所含腳本不會修改dom,因此代碼能安全的延遲執行(但是它并非所有瀏覽器兼容)
帶有defer屬性的script標簽可以放置在文檔的任何位置,對應的javascript文件將在頁面解析到script標簽時開始下載,但并不會執行,直到dom加載完成(不僅僅外鏈腳本,內嵌腳本也一樣)
也就是說帶有defer屬性的javascript文件下載時,它不會阻塞瀏覽器的其它進程,因此它可以與頁面的其它資源并行下載
2、動態腳本元素:因為script元素與頁面其它元素沒有實質性的差別,利用這點,可以通過js創建script并添加到頁面上,這樣做的好處在于:無論何時啟動下載,文件的下載和執行過程都不會阻塞頁面其它進程,你甚至可以把代碼放到頁面head區域而不會影響頁面其它部分
通常來講,把新創建的script標簽添加到head標簽里比添加到body里更保險 ,尤其是在頁面加載過程中執行代碼時更是如此,因為body中的內容沒有全部加載完成,這時...
使用動態腳本節點下載文件后,返回的代碼通常會立即執行(這樣也會有一個問題,所以你必須確保代碼中需要調用的接口都已經準備就緒)
script元素提供一個readyState屬性,它有5種取值
- uninitialized初始狀態
- loading開始下載
- loaded下載完成
- interactive數據完成下載但尚不可用
- complete所有數據已準備就緒
/* *@desc:兼容所有瀏覽器的動態加載script *@param:url script的src *@param:callback 回調函數 */ function loadScript(url, callback) { var script = document.createElement('script'); script.type = 'text/javascript'; if (script.readyState) {//IE script.onreadystatechange = function () { //IE在標識最終狀態readyState的值時并不一致,有時到達loaded狀態而不到達complete,有時候甚至不經過loaded就到達complete狀態, //所以靠譜的方式就是同時檢查這兩種狀態 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); }
使用如上代碼的方法動態加載js,瀏覽器不會保證執行的順序
3、使用XMLHttpRequest(XHR),也就是通過創建一個XHR通過,然后用它下載js文件,最后動態創建script元素將代碼注入頁面中
var xhr = new XMLHttpRequest(); xhr.open('get', 'file1.js', true); xhr.onreadystatechange = function () { if (xhr.readyState == 4) { if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {//2XX表示有效響應 304表示從緩存中讀取 var script = document.createElement('script'); script.type = 'text/javascript'; script.text = xhr.responseText; document.body.appendChild(script); } } } xhr.send(null);
使用XHR的特性:
- 下載javascript代碼,但不立即執行
- 同樣的代碼在所有瀏覽器中都能正常使用
- 請求的js文件必須與所請求的頁面牌相同的域
推薦的無阻塞模式

<script> /* *@desc:兼容所有瀏覽器的動態加載script *@param:url script的src *@param:callback 回調函數 */ function loadScript(url, callback) { var script = document.createElement('script'); script.type = 'text/javascript'; if (script.readyState) {//IE script.onreadystatechange = function () { //IE在標識最終狀態readyState的值時并不一致,有時到達loaded狀態而不到達complete,有時候甚至不經過loaded就到達complete狀態, //所以靠譜的方式就是同時檢查這兩種狀態 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函數直接嵌入頁面,從而避免產生一次http請求 loadScript('theRest.js', function () { //加載初始化頁面所需的剩下的代碼 //好處1)確保js執行過程中不會阻塞頁面其它內容的顯示 //2)當第二個js文件完成下載時,應用所需的所有dom結構已創建完畢,并做好了交互的準備,從而避免了需要另一個事件 }); </script>
可以去了解一下LazyLoad類庫,它是一個更為通用的延遲加載工具
看看我的另一篇博文 LABjs(類似于LazyLoad,但它更加方便管理依賴關系)
總結延遲加載
數據訪問相關性能優化法則
數據的存儲位置關系到訪問速度
1、如果某個跨作用域的值在函數中被引用一次以上,那么就把它存儲到局部變量中
2、有兩個語句可以在執行時臨時改變作用域鏈,一個是with,一個是catch(深入理解了作用域鏈后就會發現with會給對象的所有屬性重新創建一個變量并把它推放到作用域的最頂端,那么原先對象的屬性就就位于作用域鏈的第二層,這樣訪問局部變量啟不是變慢了) 避免使用with語句
3、catch同with原理差不多,如果代碼發生錯誤,執行過程會自動跳轉到catch子句,然后把異常對象推入一個可變對象并置于作用域的頭部,在catch代碼塊內部,函數所有局部變量將會被放到第二個作用域鏈對象中,一個很好的模式是將錯誤委托給一個函數處理(catch子句執行完畢,作用域鏈就會返回到原先的狀態)
try { methodMayCauseError(); } catch (ex) { handlerError(ex);//委托給處理器方法 //由于只執行一條語句,且沒有局部變量的訪問,作用域鏈的臨時改變就不會影響代碼性能 }
4、無論是with、try-catch、eval都被認為是動態作用域,因為動態作用域只存在于代碼執行過程中。因此無法通過靜態分析檢測出來。所以只有確實有必要的時候才推薦使用動態作用域
5、閉包允許函數訪問局部作用域之外的數據,但也因此引發了內存性能問題(同樣可以使用局部變量的方式將常用的跨作用域變量存儲起來)
6、對象在原型鏈中存在的位置越深,找到它也就越慢。同理對象成員嵌套越深,訪問速度就會越慢。這也就是為什么多次讀取同一個對象屬性,最佳做法是將屬性值保存在局部變量中
(通過點表示法object.name操作和通過括號表示法object['name']操作事實上性能沒有明顯區別,只有在safari中,點符號始終要快)
總結數據訪問
DOM編程
因為瀏覽器內部機制是把DOM和JavaScript獨立實現的,這樣也就說明兩個相互獨立的功能只能通過接口彼此連接,就會產生消耗
修改頁面區域的最佳方案是使用innerHTMl比document.createElement方法稍微會快些
使用element.cloneNode替代document.createElement方法會更有效率,但也不是特別明顯
document.getElementsByName();
document.getElementsByClassName();
document.getElementsByTagName();
document.images;
document.links;
document.forms;
document.forms[0].elements;
上面代碼中返回的對象都是HTML集合對象,它們是處于一種實時狀態的,也就是說它一直與文檔保持連接,每次你需要最新信息時,都會重復執行查詢的過程(即使你只是訪問集合的length屬性) 同樣的道理,訪問集合元素時也最好把集合存儲在局部變量中,并把length緩存在循環外部
function toArray(coll) { for (var i = 0,a=[],len=coll.length; i < len; i++) { a[i] = coll[i]; } return a; }
在dom中查找元素時,用的最多的恐怕就是childNodes和nextSibling,但如果你的瀏覽器需要兼容IE的話,推薦使用nextSibling方法來查看DOM節點
DOM屬性諸如childNodes、firstChild、nextSibling都不區分元素節點和其它類型的節點,比如注釋和文本節點,很多情況下,只需要訪問元素節點,因此在循環中需要檢查返回節點的類型并過濾掉非元素節點,但這些類型檢查都是不必要的DOM操作,使用children替代childNodes會更快
如果瀏覽器具有原生的QuerySelectorAll()方法和querySelector()方法盡量使用它們(因為自己實現它們的功能需要編寫特別多的代碼,還有就是任何編程語言都有一個共通的特性就是原生方法永遠是性能最佳的)
重排與重繪
當DOM變化影響到元素的幾何屬性時,瀏覽器就需要重新計算元素的幾何屬性,同樣其它元素的幾何屬性和位置也會因此受到影響。瀏覽器會使渲染樹中受到影響的部分失控,并重新構造渲染樹,這個過程叫重排,完成重排后,瀏覽器會重新繪制受影響的部分到屏幕中,該過程叫重繪
重排什么時候發生呢?
- 添加或刪除可見的DOM元素
- 元素位置改變
- 元素尺寸改變
- 內容改變
- 頁面瀏覽器初始化
- 瀏覽器窗口尺寸改變
需要注意的是訪問以下屬性,會觸發重排(所以在修改樣式的過程中,最好避免使用上面列出的屬性)
最小化重排與重繪
合并所有改變然后一次處理,這樣就只修改dom一次,1)使用cssText屬性來實現 2)修改css的類名稱來更改
緩存布局信息(比如說偏移量offsets、滾動位置或計算出的樣式值computedstyle values 盡量減少布局信息的獲取次數,獲取后賦值給局部變量,然后再操作局部變量)
通過以下步驟來減少重排與重繪的次數
- 從文檔流中摘下此元素
- 對其應用多重改變
- 將元素帶回文檔中
有三種方法可以使dom脫離文檔
- 隱藏元素,對其進行修改后再顯示
- 使用文檔片斷在已存DOM之外創建一個子樹,然后將它拷貝到文檔中(推薦使用此方法,因為它是涉及最小數量的DOM操作和重排)
- 將原始元素拷貝到一個脫離文檔的節點中,修改副本,完成后然后覆蓋原始元素
for (var i = 0; i < 1000; i++) { var el = document.createElement('p'); el.innerHTML = i; document.body.appendChild(el); } //可以替換為: var frag = document.createDocumentFragment(); for (var i = 0; i < 1000; i++) { var el = document.createElement('p'); el.innerHTML = i; frag.appendChild(el); } document.body.appendChild(frag);
如果是動畫元素的話,最好使用絕對定位以讓它不在文檔流中,這樣的話改變它的位置不會引起頁面其它元素重排(也就是說如果是動畫元素的話最好是使用絕對定位以讓它不在文檔流中,這樣的話改變它的位置不會引起頁面其它元素重排,只會影響它自己重排。)
IE下如果你有大量元素使用了:hover,那么會降低響應速度
DOM編程小結
文章列表