參考:《高性能JavaScript》
<body>
標籤以前,不會渲染頁面的任何部分(所以將腳本放到body標籤底部,能夠減小對頁面的阻塞時間)defer
屬性異步加載腳本defer
下載完成後不會立刻執行,直到DOM加載完成,onload事件被觸發前才執行async
--HTML5的屬性,也是異步加載腳本,一加載完就執行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); }
減小對JavaScript對頁面加載性能影響的幾種辦法:javascript
</body>
標籤閉合以前,將全部的<script>
標籤放到頁面底部。這能確保在腳本執行前已經完成渲染。<script>
標籤越少,加載也就越快,響應也更迅速。不管外鏈仍是內嵌腳本都是如此。有多種無阻塞下載JavaScript的方法:php
<scipt>
標籤的defer
或async
屬性;<scipt>
元素來下載並執行代碼;字面量:css
本地變量:java
var
定義的數據存儲單元。數組元素:正則表達式
對象成員:算法
字面量和局部變量的訪問速度快於數組項和對象成員的訪問速度。
若是跨做用域的值在函數中被引用一次以上,那麼就把它存儲到局部變量裏。
with:shell
with
語句時,執行環境的做用域鏈臨時被改變了。一個新的變量對象被建立,它包含了參數指定的對象的全部屬性。這個對象被推入做用域鏈的首位。try-catch:編程
try
代碼塊中發生錯誤,執行過程會自動跳轉到catch
子句,而後把異常對象推入一個變量對象並置於做用域的首位。catch
子句執行完畢,做用域鏈就會返回到以前的狀態。
儘可能簡化代碼來使的
catch
子句對性能的影響最小化,將錯誤委託給一個函數來處理。
只有在確實有必要時才推薦使用動態做用域。
對象能夠有兩種成員類型:實例成員(也稱own成員)和原型成員。json
hasOwnProperty()
方法能夠判斷對象是否包含特定的實例成員。數據存儲的位置會對代碼總體性能產生重大的影響。跨域
with
語句,由於它會改變執行環境做用域鏈。一樣,try-catch
語句中的catch
子句也有一樣的影響,所以也要當心。兩個相互獨立的東西經過接口鏈接就會很慢,==要儘量減小訪問次數==。
通用的法則是:減小訪問DOM的次數,把運算儘可能留在ECMAScript這一端處理。
innerHTML
比DOM方法documnet.createElemnt()
要快。element.cloneNode()
比建立節點document.createELemnt()
要快。HTML集合是包含了DOM節點引用的類數組對象,一直和文檔保持鏈接,每次訪問都要重複執行查詢的過程,是低效之源。
length
就夠了。nextSibling
比childNode
表現優異。children
比chidNodes
快。querySelectorALl()
比使用JavaScript和DOM來遍歷查找元素要快。當DOM變化影響元素的寬高和佈局改變就會引起瀏覽器「重排(reflow)」。
什麼時候觸發
手動觸發,最好避免使用的屬性
offsetTop, offsetLeft, offsetWidth, offsetHeight
scrollTop, scrollLeft, scrollWidth, scrollHeight
clientTop, clientLeft, clientWidth, clientHeight
getComputedStye() (currentStyle in IE)
合併全部的改變而後一次處理,這樣只會修改DOM一次。使用
cssText
屬性能夠實現
當你須要對DOM元素進行一系列操做時,可經過如下步驟來減小重繪和重排的次數:
使元素脫離文檔流
使用如下步驟能夠避免頁面中的大部分重排:
根據DOM標準,每一個事件都要經歷三個階段:
訪問和操做DOM是現代Web應用的重要部分。可是每次穿越鏈接ECMAScript和DOM兩個島嶼之間的橋樑,都會被收取「過橋費」。爲了減小DOM編程帶來的性能損失,請記住如下幾點:
querySelectorAll()
和firstElementChild
。循環類型:
for
循環:由四部分組成:初始化、前側條件、後執行條件、循環體。while
循環:最簡單的前側循環,由一個前側條件和一個循環體構成。任何for
都能改寫成while
循環,反之亦然 do-while
: do-while
是JavaScript中惟一一種後側循環,由循環體和後側條件構成。for-in
循環: 枚舉任何對象的屬性名。所包含的屬性包括對象實例屬性以及從原型鏈繼承而來的屬性。for-in
循環比其它幾種明顯要慢,除非你須要迭代一個屬性數量未知的對象,不然應該避免使用for-in
循環循環性能:
優化循環:
減小迭代工做量:
減小迭代次數: 達夫設備(Dufff's Device)
var i = item.length % 8; while(i){ process(item[i--]); } i = Math.floor(item.length / 8); while(i){ process(item[i--]); process(item[i--]); process(item[i--]); process(item[i--]); process(item[i--]); process(item[i--]); process(item[i--]); process(item[i--]); }
forEach()
)快8倍if-else
對比switch
: 大多數狀況下switch
比if-else
運行的要快,但只有當條件數量很大時才快的明顯。當條件數量較少時使用if-else
,在條件數量較大時使用switch
。
if-else
: 最小化到達正確分支前所需判斷的條件數量。最簡單的優化方法是確保最可能出現的條件放在首位。另外一種減小條件判斷次數的方法是把if-else
組織成一系列嵌套的if-else
語句。(使用二分法把值域分紅一系列的區間)當有大量離散值須要測試時,if-else
和switch
比使用查找錶慢不少。
爲了能在瀏覽器中安全地工做,建議改用迭代、Memoization,或者結合二者使用。
/** * @params {function} fundamental 須要增長緩存的函數 * @params {object} cache 可選的緩存對象 * @return {function} shell 加入緩存功能的函數 **/ function memoize(fundamental, cache){ cache = cache || {}; var shell = function(arg){ if(!cache.hasOwnProperty(arg)){ cache[arg] = fundamental(arg); } return cache[arg]; } return shell; }
JavaScript和其它編程語言同樣,代碼的寫法和算法的會影響運行時間。於其餘語言不一樣的是,JavaScript可用資源有限,所以優化技術更爲重要。
for
、while
、do-while
循環性能特性至關,並無一種循環類型明顯快於或慢於其餘類型。for-in
循環,除非你須要遍歷一個屬性數未知的對象。switch
老是比if-else
快,但並不老是最佳解決方案。if-else
和switch
更快。運行的代碼數量越大,使用這些策略所帶來的性能提高也就越明顯。
密集的字符串操做和草率地編寫正則表達式可能產生嚴重的性能障礙,本章提供的建議會幫助你避免這些常見的陷阱。
使用定時器處理數組
知足兩個條件:
function processArray(items, process, callback){ var todo = items.concat(); // 克隆原數組 // 創建一個定時器 setTimeout(function(){ process(todo.shift()); // 處理函數 // 數組內若是還有數據,再執行一次當前函數,不然觸發回調函數 if (todo.length > 0){ setTimeout(argument.callee, 25); }else{ callback(items); } }, 25); }
分割任務
function multistep(steps, args, callback){ var tasks = step.concat(); // 克隆數組 setTimeout(function(){ // 執行下一個任務 var task = tasks.shift(); task.apply(null, args || []); // 檢查是否還要其它任務 if(tasks.length > 0){ setTimeout(arguments.callee, 25); }else{ callback(); } }, 25); } <!--調用方法--> function saveDocument(id){ var tasks = [openDocumnet, writeText, closeDocument, updateUI]; multistep(tasks, [id], function(){ alert("Save completed!") }); }
記錄代碼運行時間
porcessArray()
方法:function timedProcessArray(items, process, callback){ var todo = items.concat(); // 克隆原始數組 setTimeout(function(){ var start = +new Date(); do{ process(todo.shift()); }while(todo.length > 0 && (+new Date() - start < 50)); if(todo.length > 0){ setTimeout(arguments.callee, 25); }else{ callback(items); } }, 25); }
其功能是JavaScript的一個子集,由以下部分組成:
navigator
對象,只包含四個屬性:appName
、appVersion
、user Agent
和platform
。location
對象(於window.loacation
相同,不過全部屬性都是隻讀的)。self
對象,指向worker對象。importScripts()
方法,用來加載Worker所用到的外部JavaScript文件。Object
、Array
、Date
等。setTimeout()
和setIntervar()
方法。close()
方法,它能馬上中止Worker運行。與Worker通訊
var worker = new Worker("code.js"); // 接受信息的事件處理器 worker.onmessage = function(event){ alert(event.data); //使用data屬性存放傳入的數據 }; worker.postMessage("Nicholas"); // 發送數據給worker <!--code.js 內部代碼--> // 加載外部文件 importScripts("file1.js", "file2.js"); self.onmessage = function(event){ self.postMessage("Hello" + event.data + "!"); }
實際應用
Web Workers適用於那些處理純數據,或者與瀏覽器UI無關的長時間運行腳本。例如:
JavaScript和用戶界面更新在同一個進程中運行,所以一次只能處理一件事。這意味着當JavaScript代碼正在運行時,用戶界面不能響應輸入,反之亦然。高效的管理UI線程就是要確保JavaScript不能運行太長時間,以避免影響用戶體驗。最後,請牢記以下幾點:
Web應用越複雜,積極主動地管理UI線程就越重要。即便JavaScript代碼再重要,也不該該影響用戶體驗。
var url = '/data.php'; var params = [ 'id=7894', 'limit=20' ]; var req = new XMLHttpRequest(); req.onreadystatechange = function(){ if(req.redyState === 4){ var responseHeaders = req.getAllResponseHeaders(); // 獲取響應頭信息 var data = req.reponseText; // 獲取數據 // 數據處理 } req.open('GET', url + '?' + params.join('&'), ture); req.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); // 設置請求頭信息 req.send(null); // 發送一個請求 }
POST和GET對比:當只獲取數據時,因使用GET請求的數據會被瀏覽器緩存,若果屢次請求同一數據的話,它有助於提高性能;當請求的URL加上參數的長度大於等於2048個字符時,才應該使用POST獲取數據(瀏覽器會限制URL長度)。
限制:
var scriptElement = doucment.createElement('script'); scriptElement.src = "http://any-domain.com/javascropt/lib.js"; document.getElementsByTagName('head')[0].appendChild(scriptElement); function jsonnCallback(jsonString){ var = eval('('+jsonString+')'); // 處理數據... } // lib.js jsonCallback({ "status": 12, "colors": [ "#fff", "#000", "#ff0000" ] });
速度很快,可是引用外部來源的代碼不安全。
在特定場合下使用能顯著提高頁面的總體性能:
var req = new XMLHttpReques(); var getLatestPacketInterval, lastLength = 0; req.open('GET', 'rollup_images.php', ture); req.onreadystatechange = readyStateHandler; req.send(null); function readyStateHandler(){ if(req.readyState === 3 && getLatestPacketInterval == null){ // 開始輪詢 getLatestPacketInterval = window.setInterval(function(){ getLatestPacket(); }, 15); } if(req.readyState === 4){ // 中止輪詢 clearInterval(getLatestPacketInterval); // 獲取最後一個數據包 getLatestPacket(); } } function getLatestPacket(){ var length = req.responseText.length; var packet = req.reponseText.substring(lastLength, length); processPacket(packet); lastLength = length; }
function xhrPost(url, params, callback){ var req = new XMLHttpRequest(); req.onerror = function(){ setTimeout(function(){ xhrPost(url, jparams, callback); }, 1000); }; req.onreadystatechange = function(){ if(req.readyState == 4){ if(callback && typeof callback === 'function'){ callback(); } } }; req.open('POST', url, ture); req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); req.setRequestHeader('Content-Type', params.length); req.send(params.join('&')); }
當使用XHR發送數據到服務器時,GEt方式會更快。這是由於,對於少許數據而言,一個GET請求往服務器只發送一個數據包。而一個POST請求,至少要發送兩個數據包,一個裝在頭信息,一個裝載POST正文。POST更適合發送大量數據到服務器,由於它不關心額外數據包的數量。
var url = '/status_tarcker.php'; var params = [ 'step=2', 'time=12316151' ]; var beacon = new Image(); beacon.src = url + '?' + params.join('&'); beacon.onload = function(){ if(this.width == 1){ // 成功 }eles if(this.width == 2){ // 失敗,請重試並建立另外一個信標 } }; beacon.onerror = function(){ // 出錯, 稍後重試並建立另外一個信標。 };
經常使用數據格式:
一般來講數據格式越輕量越好,JSON和字符分割的自定義格式是最好的。若是數據集很大而且對解析時間有要求,那麼就從以下兩種格式中作出選擇:
split()
解析。這項技術解析大數據集比JSON-P略快,並且一般文件尺寸更小。緩存數據
//本地數據存儲:把響應文本保存到一個對象中,以URL爲鍵值做爲索引。 var localCache = {}; function xhrRequest(url, callback){ // 檢查此URL的本地緩存 if(localCache[url]){ callback.success(localCahce[url]); return; } // 此URL對應的緩存沒有找到,則發送請求 var req = new createXhrObject(); req.onerror = function(){ callback.error(); }; req.onreadystatechange = function(){ if(req.readyState == 4){ if(req.responseText === '' || req.status == '404'){ callback.error(); return; } // 存儲響應文本到本地緩存 localCache[url] = req.responseText; callback.success(req.responseText); } }; req.open("GET", url, true); req.send(null); } // 刪除緩存 delete localCache['/user/friendlist/'];
高性能的Ajax包括如下方面: 瞭解你項目的具體需求,選擇正確的數據格式和與之匹配的傳輸技術。
做爲數據格式,純文本和HTML只適用於特定場合,但它們能夠節省客戶端的CUP週期。XML被普遍應用並且支持良好,可是它十分笨重並且解析緩慢。JSON是輕量級的,解析速度快(被視爲原生代碼而不是字符串),通用性與XML至關。字符分割的自定義格式十分輕量,在解析大量數據集時很是快,但需編寫額外的服務端構造函數,並在客戶端解析。
當從頁面當前所處的域下請求數據時,XHR提供了最完善的控制和靈活性,儘管它會把接受到的全部數據當成一個字符串,且這有可能下降解析速度。另外一方面,動態腳本注入容許跨域請求和本地執行JavaScript和JSON可是它的接口不那麼安全,並且還不能讀取頭信息或相應代碼。Multiple XHR能夠用來減小請求數,並處理一個響應的各類文件類型,可是它不能緩存接收到的響應。當須要發送數據時,圖片信標是一種簡單而有效的方法。XHR還能夠用POST方法發送大量數據。
除了這些格式和傳輸數據,還有一些準則有助於加速你的Ajax:
Ajax爲提高你的網站的潛在性能提供了廣闊的空間,由於許多網站大量使用異步請求,並且它還提供了一些與它無關的問題的解決方案,好比有過多的資源須要加載。在XHR創造性地使用是一個反應遲鈍且平淡無奇的頁面與響應快速且高效的頁面的區別所在;是一個用戶痛恨使用的站點與用戶迷戀的站點的區別所在。
在JavaScript代碼中執行另一段代碼時,都會致使雙重求值的性能消耗。
var num1 = 5, num2 = 6, // eval()執行代碼字符串 result = eval("num1 + num2"); // Function()執行代碼字符串 sum = new Function("arg1", "arg2", "return arg1 + arg2"); // setTime()執行代碼字符串 setTime("sum = num1 + num2", 100); // settInterval() 執行代碼字符串 setInterval("sum = num1 + num2", 100);
雙重求值是一項代價昂貴的操做,它比直接包含的代碼執行速度要慢許多。
使用直接量是建立對象和數組最快的方式。
別作可有可無的工做,別重複作已經完成的工做。
function addHandler(target, eventType, handler){ // 覆寫現有函數 if(target.addEventListener){ // DOM2 Events addHandler = function(target, eventType, handler){ target.addEventListener(eventType, handler, false); }; }else { // IE addHandler = function(target, evetType, handler){ target.attachEvent("on" + eventType, handler); }; } //調用新函數 addHandler(target, eventType, handler); } function removeHandler(target, eventType, handler){ // 覆寫現有函數 if(target.removeEventListener){ //DOM2 Events removeHandler = function(target, eventType, handler){ target.removeEventListener(eventType, handler, false); }; }else{ //IE removeHandler = function(target, eventType, handler){ target.detachEvent("on" + eventType, handler); }; } // 調用新函數 removeHanlder(target, eventType, handler); }
var addHandler = document.body.addEventListenre ? function(target, efvent, handler){ target.addEventListener(eventTye, handler, false); }: function(target, eventType, handler){ target.attachEvent("on" + eventType, handler); }; var removeHanlder = document.body.removeEventListener ? function(target, efvent, handler){ target.removeEventListener(eventTye, handler, false); }: function(target, eventType, handler){ target.detachEvent("on" + eventType, handler); };
for(var i = 0; i < length; i < len; i++){ if(i & 1){ className = "odd"; }else{ className = "even"; } }
常量 | 值 |
---|---|
Math.E |
E的值,天然對數的底 |
Math.LN10 |
10的天然對數 |
Math.LN2 |
2的天然對數 |
Math.LOG2E |
以2爲底的E的對數 |
Math.LOG10E |
以10爲底的E的對數 |
Math.PI |
π的常量 |
Math.SQRT1_2 |
1/2的平方根 |
Math.SQRT2 |
2的平方根 |
方法 | 含義 |
---|---|
Math.abs(num) |
返回num的絕對值 |
Math.exp(num) |
返回E的指數 |
Math.log(num) |
返回num的天然對數 |
Math.pow(num) |
返回num的power次冪 |
Math.sqrt(num) |
返回num的平方根 |
Math.acos(x) |
返回x的反餘弦值 |
Math.asin(x) |
返回x的反正弦值 |
Math.atan(x) |
返回x的反正切值 |
Math.atan2(y, x) |
返回從X軸到(y,x)點的角度 |
Math.cos(x) |
返回x的餘弦值 |
Math.sin(x) |
返回x的正弦值 |
Math.tan(x) |
返回x的正切值 |
JavaScript提出了一些獨一無二的性能挑戰,這與你組織代碼的方式有關。隨着Web應用變得愈來愈高級,包含的JavaScript代碼也愈來愈多,各類模式和反模式也逐漸出現。爲了編寫更高效的代碼,請牢記如下編程實踐:
eval()
和Function()
構造器來避免雙重求值帶來的性能消耗。一樣的,給setTimeout()
和setInterval()
傳遞函數而不是字符串做爲參數。當本書涵蓋了大量的優化技術和方法,把這些方案應用在那些被頻繁使用的代碼上時,你將會看到顯著的性能提高。
構建和部署的過程對基於JavaScript的Web應用的性能有着巨大影響。這個過程當中最重要的步驟有:
全部這些步驟都應該自動化處理,可使用公開的工具,好比Apache Ant,也可使用定製的工具來知足你的特定需求。若是你使得構建工程工做起來,你將會顯著提升那些依賴大量JavaScript的Web應用或網站的性能。
當網頁或Web應用變慢時,分析從網絡下載的資源以及分析腳本的運行性能能讓你專一於那些最須要優化的地方。
這些工具會幫助你深刻了解你的代碼在那些一般你比較陌生的編程環境下是如何運行的。在開始優化工做以前先使用它們,以確保開發時間在刀刃上。