該屬性代表腳本在執行期間不會影響到頁面的構造,腳本會先下載但被延遲到整個頁面都解析完畢後再運行.只適用於外部腳本javascript
<script src="js/test.js" defer></script> <div>123</div> <script> alert('script'); window.onload = function(){ alert('loaded'); } </script> <!-- 輸出順序 123 script defer loaded -->
只適用於外部腳本,告訴瀏覽器當即下載腳本,同時不影響頁面的解析,先下載完先執行,沒有必定的順序,但必定會在頁面load事件前執行php
有幾種方法能夠減小JavaScript 對性能的影響:html
<script>
標籤放置在頁面的底部,緊靠</body>
的上方。此法能夠保證頁面在腳本<script>
標籤越少,頁面的加載速度就越快,響應也更加迅速。不論外部腳本文件仍是內聯代碼都是如此。<script>
標籤添加defer屬性<script>
元素,用它下載並執行代碼在JavaScript 中有四種基本的數據訪問位置:前端
總的來講,直接量和局部變量的訪問速度要快於數組項和對象成員的訪問速度。java
此過程搜索運行期上下文的做用域鏈,查找同名的標識符。搜索工做從運行函數的激活目標之做用域鏈的前端開始。若是找到了,那麼就使用這個具備指定標識符的變量;若是沒找到,搜索工做將進入做用域鏈的下一個對象。此過程持續運行,直到標識符被找到,或者沒有更多對象可用於搜索,這種狀況下標識符將被認爲是未定義的。web
在運行期上下文的做用域鏈中,一個標識符所處的位置越深,它的讀寫速度就越慢。因此,函數中局部變量的訪問速度老是最快的,而全局變量一般是最慢的(優化的JavaScript引擎在某些狀況下能夠改變這種情況)。請記住,全局變量老是處於運行期上下文做用域鏈的最後一個位置,因此老是最遠才能觸及的。
你能夠經過這種方法減輕重複的全局變量訪問對性能的影響:首先將全局變量的引用存儲在一個局部變量中,而後使用這個局部變量代替全局變量。ajax
with 表達式
當代碼流執行到一個with 表達式時,運行期上下文的做用域鏈被臨時改變了。一個新的可變對象將被建立,它包含指定對象的全部屬性。此對象被插入到做用域鏈的前端,意味着如今函數的全部局部變量都被推入第二個做用域鏈對象中,因此訪問代價更高了。
try-catch 表達式的catch 子句
當try 塊發生錯誤時,程序流程自動轉入catch 塊,並將異常對象推入做用域鏈前端的一個可變對象中。在catch塊中,函數的全部局部變量如今被放在第二個做用域鏈對象中。正則表達式
一般,一個函數的激活對象與運行期上下文一同銷燬。當涉及閉包時,激活對象就沒法銷燬了,由於引用仍然存在於閉包的[[Scope]]屬性中。這意味着腳本中的閉包與非閉包函數相比,須要更多內存開銷。算法
function assignEvents(){ var id = "xdi9592"; document.getElementById("save-btn").onclick = function(event){ saveDocument(id); }; }
注意閉包中使用的兩個標識符,id 和saveDocument,存在於做用域鏈第一個對象以後的位置上。這是閉包最主要的性能關注點:你常常訪問一些範圍以外的標識符,每次訪問都致使一些性能損失。
關於域外變量的處理建議,減輕對運行速度的影響:將經常使用的域外變量存入局部變量中,而後直接訪問局部變量。編程
每深刻原形鏈一層都會增長性能損失。記住,搜索實例成員的過程比訪問直接量或者局部變量負擔更重,因此增長遍歷原形鏈的開銷正好放大了這種效果。
因爲對象成員可能包含其它成員,例如不太常見的寫法window.location.href這種模式。每遇到一個點號,JavaScript 引擎就要在對象成員上執行一次解析過程。成員嵌套越深,訪問速度越慢。
通常來講,若是在同一個函數中你要屢次讀取同一個對象屬性,最好將它存入一個局部變量。以局部變量替代屬性,避免多餘的屬性查找帶來性能開銷。在處理嵌套對象成員時這點特別重要
在JavaScript中,數據存儲位置能夠對代碼總體性能產生重要影響。有四種數據訪問類型:直接量,變量,數組項,對象成員。它們有不一樣的性能考慮。
通常來講,你能夠經過這種方法提升JavaScript 代碼的性能:將常用的對象成員,數組項,和域外變量存入局部變量中。而後,訪問局部變量的速度會快於那些原始變量。
訪問或修改元素最壞的狀況是使用循環執行此操做,特別是在HTML 集合中使用循環。
function innerHTMLLoop() { for (var count = 0; count < 15000; count++) { document.getElementById('here').innerHTML += 'a'; } }
這段代碼的問題是,在每次循環單元中都對DOM 元素訪問兩次:一次讀取innerHTML 屬性內容,另外一次寫入它。
一個更有效率的版本將使用局部變量存儲更新後的內容,在循環結束時一次性寫入:
function innerHTMLLoop2() { var content = ''; for (var count = 0; count < 15000; count++) { content += 'a'; } document.getElementById('here').innerHTML += content; }
它們的性能如何?答案是:性能差異不大,可是,在全部瀏覽器中,innerHTML 速度更快一些,除
了最新的基於WebKit 的瀏覽器(Chrome 和Safari)。
HTML 集合是用於存放DOM 節點引用的類數組對象。下列函數的返回值就是一個集合:
<img>
元素<a>
元素這些方法和屬性返回HTMLCollection 對象,是一種相似數組的列表。它們不是數組(由於它們沒有諸
如push()或slice()之類的方法),可是提供了一個length屬性,和數組同樣你可使用索引訪問列表中的元素。
HTML 集合實際上在查詢文檔,當你更新信息時,每次都要重複執行這種查詢操做。例如讀取集合中元
素的數目(也就是集合的length),這正是低效率的來源。
優化的辦法很簡單,只要將集合的length 屬性緩存到一個變量中,而後在循環判斷條件中使用這個變
量
function loopCacheLengthCollection() { var coll = document.getElementsByTagName('div'), len = coll.length; for (var count = 0; count < len; count++) { //more code } }
通常來講,對於任何類型的DOM 訪問,若是同一個DOM 屬性或方法被訪問一次以上,最好使用一個
局部變量緩存此DOM成員。當遍歷一個集合時,第一個優化是將集合引用存儲於局部變量,並在循環以外緩存length 屬性。而後,若是在循環體中屢次訪問同一個集合元素,那麼使用局部變量緩存它。
querySelectorAll()
IE8+此函數不返回HTML集合,因此返回的節點不呈現文檔的「存在性結構」。這就避免了本章前面提到的HTML 集合所固有的性能問題(以及潛在的邏輯問題)。
當你查詢佈局信息如偏移量、滾動條位置,或風格屬性時,瀏覽器刷新隊列並執行全部修改操做,以返回最新的數值。最好是儘可能減小對佈局信息的查詢次數,查詢時將它賦給局部變量,並用局部變量參與計算。
重排版和重繪代價昂貴,因此,提升程序響應速度一個好策略是減小此類操做發生的機會。爲減小發生次數,你應該將多個DOM 和風格改變合併到一個批次中一次性執行。
當你須要對DOM 元素進行屢次修改時,你能夠經過如下步驟減小重繪和重排版的次數:
經常使用方法:
javascript var ul = document.getElementById('mylist'); ul.style.display = 'none'; appendDataToElement(ul, data); ul.style.display = 'block';
javascript var fragment = document.createDocumentFragment(); appendDataToElement(fragment, data); document.getElementById('mylist').appendChild(fragment);
推薦儘量使用文檔片段(第二種解決方案)由於它涉及最少數量的DOM 操做和重排版
使用如下步驟能夠避免對大部分頁面進行重排版:
當頁面中存在大量元素,並且每一個元素有一個或多個事件句柄與之掛接(例如onclick)時,可能會影
響性能
一個簡單而優雅的處理DOM 事件的技術是事件託管。它基於這樣一個事實:事件逐層冒泡總能被父元
素捕獲。採用事件託管技術以後,你只須要在一個包裝元素上掛接一個句柄,用於處理子元素髮生的全部事件。
爲減小DOM 編程中的性能損失,請牢記如下幾點:
在JavaScript 提供的四種循環類型中,只有一種循環比其餘循環明顯要慢:for-in 循環。
因爲每次迭代操做要搜索實例或原形的屬性,for-in 循環每次迭代都要付出更多開銷。所以推薦的作法以下:
除非你須要對數目不詳的對象屬性進行操做,不然避免使用for-in 循環其餘循環類型性能至關,難以肯定哪一種循環更快。選擇循環類型應基於需求而不是性能。
items.forEach(function(value, index, array){ process(value); });
儘管基於函數的迭代顯得更加便利,它仍是比基於循環的迭代要慢一些。
大多數狀況下switch 表達式比if-else 更快,但只有當條件體數量很大時才明顯更快。二者間
的主要性能區別在於:當條件體增長時,if-else 性能負擔增長的程度比switch更多。所以,咱們的天然傾向認爲條件體較少時應使用if-else 而條件體較多時應使用switch 表達式,若是從性能方面考慮也是正確的。
優化if-else的目標老是最小化找到正確分支以前所判斷條件體的數量。最簡單的優化方法是將最多見的條件體放在首位
str += "one" + "two"; str = str + "one" + "two";
這就避免了使用臨時字符串,由於賦值表達式開頭以str爲基礎,一次追加一個字符串,從左至右依次
鏈接。
str = ["a","b","c"].join("");
str = str.concat(s1, s2, s3);
這是一個有必要的限制,確保惡意代碼編寫者不能經過無盡的密集操做鎖定用戶瀏覽器或計算機。此類限制有兩個:調用棧尺寸限制和長時間腳本限制。
function processArray(items, process, callback){ var todo = items.concat(); //create a clone of the original setTimeout(function(){ process(todo.shift()); //if there's more items to process, create another timer if(todo.length > 0){ setTimeout(arguments.callee, 25); } else { callback(items); } }, 25); }
setTimeout第二個參數指出何時應當將任務添加到UI 隊列之中,並非說那時代碼將被執行。
若是函數運行時間太長,它能夠拆分紅一系列更小的步驟,把獨立方法放在定時器中調用。你能夠將每一個函數都放入一個數組,而後使用前一節中提到的數組處理模式:
function saveDocument(id){ var tasks = [openDocument, writeText, closeDocument, updateUI]; setTimeout(function(){ //execute the next task var task = tasks.shift(); task(id); //determine if there's more if (tasks.length > 0){ setTimeout(arguments.callee, 25); } }, 25); }
可經過原生的Date 對象跟蹤代碼的運行時間。這是大多數JavaScript 分析工具所採用的工做方式:
var start = +new Date(), stop; someLongProcess(); stop = +new Date(); if(stop-start < 50){ alert("Just about right."); } else { alert("Taking too long."); }
工人線程的運行環境由下列部分組成:
要建立網頁工人線程,你必須傳入這個JavaScript 文件的URL:
var worker = new Worker("code.js");
工人線程和網頁代碼經過事件接口進行交互。網頁代碼可經過postMessage()方法向工人線程傳遞數據,它接收單個參數,即傳遞給工人線程的數據。此外,在工人線程中還有onmessage事件句柄用於接收信息。
當工人線程經過importScripts()方法加載外部JavaScript 文件,它接收一個或多個URL參數,指出要加載的JavaScript文件網址。工人線程以阻塞方式調用importScripts(),直到全部文件加載完成並執行以後,腳本才繼續運行。因爲工人線程在UI線程以外運行,這種阻塞不會影響UI響應。
網頁工人線程適合於那些純數據的,或者與瀏覽器UI 不要緊的長運行腳本。它看起來用處不大,而網
頁應用程序中一般有一些數據處理功能將受益於工人線程,而不是定時器。
任何超過100毫秒的處理,都應當考慮工人線程方案是否是比基於定時器的方案更合適。固然,還要基
於瀏覽器是否支持工人線程。
有效地管理UI 線程就是要確保JavaScript 不能運行太長時間,以避免影響用戶體驗。最後,請牢記以下幾點:
在現代高性能JavaScript 中使用的三種技術是XHR,動態腳本標籤插入和多部分的XHR。
var url = '/data.php'; var params = ['id=934875','limit=20']; var req = new XMLHttpRequest(); req.onreadystatechange = function() { if (req.readyState === 4) { var responseHeaders = req.getAllResponseHeaders(); // Get the response headers. var data = req.responseText; // Get the data. // Process the data here... } } req.open('GET', url + '?' + params.join('&'), true); req.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); // Set a request header. req.send(null); // Send the request.
var scriptElement = document.createElement('script'); scriptElement.src = 'http://any-domain.com/javascript/lib.js'; document.getElementsByTagName('head')[0].appendChild(scriptElement);
這裏介紹最新的技術,多部分XHR(MXHR)容許你只用一個HTTP 請求就能夠從服務器端獲取多個
資源。它經過將資源(能夠是CSS 文件,HTML 片斷,JavaScript代碼,或base64編碼的圖片)打包成一個由特定分隔符界定的大字符串,從服務器端發送到客戶端。
使用此技術有一些缺點,其中最大的缺點是以此方法得到的資源不能被瀏覽器緩存
大多數狀況下,不必使用eval_r()或Function(),若是可能的話,儘可能避免使用它們。至於另外兩個函數,setTimeout()和setInterval(),建議第一個參數傳入一個函數而不是一個字符串。
位與& 兩個操做數的位都是1,結果纔是1
位或| 有一個操做數的位是1,結果就是1
位異^或 兩個位中只有一個1,結果纔是1
位非~ 遇0返回1,反之亦然
for (var i=0, len=rows.length; i < len; i++){ if (i & 1) { //判斷奇偶數 className = "odd"; } else { className = "even"; } }