摘要:前端性能優(yōu)化指南優(yōu)化緩存異步并不等于即時(shí)。操作性能問題主要有以下原因。發(fā)生在之前,所以相對來說會造成更多性能損耗。新引擎還對對象屬性訪問做了優(yōu)化,解決方案叫,簡稱。代價(jià)是前置的掃描類型編譯優(yōu)化。數(shù)組,,閉包變量不在優(yōu)化范疇之列。
前端性能優(yōu)化指南 AJAX優(yōu)化
COOKIE專題
緩存AJAX:
異步并不等于即時(shí)。
請求使用GET:
當(dāng)使用XMLHttpRequest時(shí),而URL長度不到2K,可以使用GET請求數(shù)據(jù),GET相比POST更快速。
POST類型請求要發(fā)送兩個(gè)TCP數(shù)據(jù)包。
先發(fā)送文件頭。
再發(fā)送數(shù)據(jù)。
GET類型請求只需要發(fā)送一個(gè)TCP數(shù)據(jù)包。
取決于你的cookie數(shù)量。
DOM優(yōu)化減少COOKIE的大小。
使用無COOKIE的域。
比如圖片CSS等靜態(tài)文件放在靜態(tài)資源服務(wù)器上并配置多帶帶域名,客戶端請求靜態(tài)文件的時(shí)候,減少COOKIE反復(fù)傳輸時(shí)對主域名的影響。
eval優(yōu)化
優(yōu)化節(jié)點(diǎn)修改。
使用cloneNode在外部更新節(jié)點(diǎn)然后再通過replace與原始節(jié)點(diǎn)互換。
var orig = document.getElementById("container");
var clone = orig.cloneNode(true);
var list = ["foo", "bar", "baz"];
var content;
for (var i = 0; i < list.length; i++) {
content = document.createTextNode(list[i]);
clone.appendChild(content);
}
orig.parentNode.replaceChild(clone, orig);
優(yōu)化節(jié)點(diǎn)添加
多個(gè)節(jié)點(diǎn)插入操作,即使在外面設(shè)置節(jié)點(diǎn)的元素和風(fēng)格再插入,由于多個(gè)節(jié)點(diǎn)還是會引發(fā)多次reflow。
優(yōu)化的方法是創(chuàng)建DocumentFragment,在其中插入節(jié)點(diǎn)后再添加到頁面。
如JQuery中所有的添加節(jié)點(diǎn)的操作如append,都是最終調(diào)用DocumentFragment來實(shí)現(xiàn)的,
createSafeFragment(document) {var list = nodeNames.split( "|" ),
safeFrag = document.createDocumentFragment();if (safeFrag.createElement) {
while (list.length) { safeFrag.createElement( list.pop(); ); };};
return safeFrag;};
優(yōu)化CSS樣式轉(zhuǎn)換。
如果需要?jiǎng)討B(tài)更改CSS樣式,盡量采用觸發(fā)reflow次數(shù)較少的方式。
如以下代碼逐條更改元素的幾何屬性,理論上會觸發(fā)多次reflow。
element.style.fontWeight = "bold" ; element.style.marginLeft= "30px" ; element.style.marginRight = "30px" ;
可以通過直接設(shè)置元素的className直接設(shè)置,只會觸發(fā)一次reflow。
element.className = "selectedAnchor" ;
減少DOM元素?cái)?shù)量
在console中執(zhí)行命令查看DOM元素?cái)?shù)量。
`document.getElementsByTagName( "*" ).length`正常頁面的DOM元素?cái)?shù)量一般不應(yīng)該超過1000。
DOM元素過多會使DOM元素查詢效率,樣式表匹配效率降低,是頁面性能最主要的瓶頸之一。
DOM操作優(yōu)化。
DOM操作性能問題主要有以下原因。
DOM元素過多導(dǎo)致元素定位緩慢。
大量的DOM接口調(diào)用。
JAVASCRIPT和DOM之間的交互需要通過函數(shù)API接口來完成,造成延時(shí),尤其是在循環(huán)語句中。
DOM操作觸發(fā)頻繁的reflow(layout)和repaint。
layout發(fā)生在repaint之前,所以layout相對來說會造成更多性能損耗。
reflow(layout)就是計(jì)算頁面元素的幾何信息。
repaint就是繪制頁面元素。
對DOM進(jìn)行操作會導(dǎo)致瀏覽器執(zhí)行回流reflow。
解決方案。
純JAVASCRIPT執(zhí)行時(shí)間是很短的。
最小化DOM訪問次數(shù),盡可能在js端執(zhí)行。
如果需要多次訪問某個(gè)DOM節(jié)點(diǎn),請使用局部變量存儲對它的引用。
謹(jǐn)慎處理HTML集合(HTML集合實(shí)時(shí)連系底層文檔),把集合的長度緩存到一個(gè)變量中,并在迭代中使用它,如果需要經(jīng)常操作集合,建議把它拷貝到一個(gè)數(shù)組中。
如果可能的話,使用速度更快的API,比如querySelectorAll和firstElementChild。
要留意重繪和重排。
批量修改樣式時(shí),離線操作DOM樹。
使用緩存,并減少訪問布局的次數(shù)。
動畫中使用絕對定位,使用拖放代理。
使用事件委托來減少事件處理器的數(shù)量。
優(yōu)化DOM交互
在JAVASCRIPT中,DOM操作和交互要消耗大量時(shí)間,因?yàn)樗鼈兺枰匦落秩菊麄€(gè)頁面或者某一個(gè)部分。
最小化現(xiàn)場更新。
當(dāng)需要訪問的DOM部分已經(jīng)已經(jīng)被渲染為頁面中的一部分,那么DOM操作和交互的過程就是再進(jìn)行一次現(xiàn)場更新。
現(xiàn)場更新是需要針對現(xiàn)場(相關(guān)顯示頁面的部分結(jié)構(gòu))立即進(jìn)行更新,每一個(gè)更改(不管是插入單個(gè)字符還是移除整個(gè)片段),都有一個(gè)性能損耗。
現(xiàn)場更新進(jìn)行的越多,代碼完成執(zhí)行所花的時(shí)間也越長。
多使用innerHTML。
有兩種在頁面上創(chuàng)建DOM節(jié)點(diǎn)的方法:
使用諸如createElement()和appendChild()之類的DOM方法。
使用innerHTML。
當(dāng)使用innerHTML設(shè)置為某個(gè)值時(shí),后臺會創(chuàng)建一個(gè)HTML解釋器,然后使用內(nèi)部的DOM調(diào)用來創(chuàng)建DOM結(jié)構(gòu),而非基于JAVASCRIPT的DOM調(diào)用。由于內(nèi)部方法是編譯好的而非解釋執(zhí)行,故執(zhí)行的更快。
對于小的DOM更改,兩者效率差不多,但對于大的DOM更改,innerHTML要比標(biāo)準(zhǔn)的DOM方法創(chuàng)建同樣的DOM結(jié)構(gòu)快得多。
回流reflow。
發(fā)生場景。
改變窗體大小。
更改字體。
添加移除stylesheet塊。
內(nèi)容改變哪怕是輸入框輸入文字。
CSS虛類被觸發(fā)如 :hover。
更改元素的className。
當(dāng)對DOM節(jié)點(diǎn)執(zhí)行新增或者刪除操作或內(nèi)容更改時(shí)。
動態(tài)設(shè)置一個(gè)style樣式時(shí)(比如element.style.width="10px")。
當(dāng)獲取一個(gè)必須經(jīng)過計(jì)算的尺寸值時(shí),比如訪問offsetWidth、clientHeight或者其他需要經(jīng)過計(jì)算的CSS值。
解決問題的關(guān)鍵,就是限制通過DOM操作所引發(fā)回流的次數(shù)。
在對當(dāng)前DOM進(jìn)行操作之前,盡可能多的做一些準(zhǔn)備工作,保證N次創(chuàng)建,1次寫入。
在對DOM操作之前,把要操作的元素,先從當(dāng)前DOM結(jié)構(gòu)中刪除:
通過removeChild()或者replaceChild()實(shí)現(xiàn)真正意義上的刪除。
設(shè)置該元素的display樣式為“none”。
每次修改元素的style屬性都會觸發(fā)回流操作。
element.style.backgroundColor = "blue";
使用更改className的方式替換style.xxx=xxx的方式。
使用style.cssText = "";一次寫入樣式。
避免設(shè)置過多的行內(nèi)樣式。
添加的結(jié)構(gòu)外元素盡量設(shè)置它們的位置為fixed或absolute。
避免使用表格來布局。
避免在CSS中使用JavaScript expressions(IE only)。
將獲取的DOM數(shù)據(jù)緩存起來。這種方法,對獲取那些會觸發(fā)回流操作的屬性(比如offsetWidth等)尤為重要。
當(dāng)對HTMLCollection對象進(jìn)行操作時(shí),應(yīng)該將訪問的次數(shù)盡可能的降至最低,最簡單的,你可以將length屬性緩存在一個(gè)本地變量中,這樣就能大幅度的提高循環(huán)的效率。
HTML優(yōu)化
避免eval:
eval會在時(shí)間方面帶來一些效率,但也有很多缺點(diǎn)。
eval會導(dǎo)致代碼看起來更臟。
eval會需要消耗大量時(shí)間。
eval會逃過大多數(shù)壓縮工具的壓縮。
JIT與GC優(yōu)化
插入HTML。
JavaScript中使用document.write生成頁面內(nèi)容會效率較低,可以找一個(gè)容器元素,比如指定一個(gè)div,并使用innerHTML來將HTML代碼插入到頁面中。
避免空的src和href。
當(dāng)link標(biāo)簽的href屬性為空、script標(biāo)簽的src屬性為空的時(shí)候,瀏覽器渲染的時(shí)候會把當(dāng)前頁面的URL作為它們的屬性值,從而把頁面的內(nèi)容加載進(jìn)來作為它們的值。
為文件頭指定Expires。
使內(nèi)容具有緩存性,避免了接下來的頁面訪問中不必要的HTTP請求。
重構(gòu)HTML,把重要內(nèi)容的優(yōu)先級提高。
Post-load(次要加載)不是必須的資源。
利用預(yù)加載優(yōu)化資源。
合理架構(gòu),使DOM結(jié)構(gòu)盡量簡單。
利用LocalStorage合理緩存資源。
盡量避免CSS表達(dá)式和濾鏡。
嘗試使用defer方式加載Js腳本。
新特性:will-change,把即將發(fā)生的改變預(yù)先告訴瀏覽器。
新特性Beacon,不堵塞隊(duì)列的異步數(shù)據(jù)發(fā)送。
不同之處:網(wǎng)絡(luò)緩慢,緩存更小,不令人滿意的瀏覽器處理機(jī)制。
盡量多地緩存文件。
使用HTML5 Web Workers來允許多線程工作。
為不同的Viewports設(shè)置不同大小的Content。
正確設(shè)置可Tap的目標(biāo)的大小。
使用響應(yīng)式圖片。
支持新接口協(xié)議(如HTTP2)。
未來的緩存離線機(jī)制:Service Workers。
未來的資源優(yōu)化Resource Hints(preconnect, preload, 和prerender)。
使用Server-sent Events。
設(shè)置一個(gè)Meta Viewport。
js載入優(yōu)化
untyped(無類型)。
JAVASCRIPT是個(gè)無類型的語言,這導(dǎo)致了如x=y+z這種表達(dá)式可以有很多含義。
y,z是數(shù)字,則+表示加法。
y,z是字符串,則+表示字符串連接。
而JS引擎內(nèi)部則使用“細(xì)粒度”的類型,比如:
32-bit* integer。
64-bit* floating-point。
這就要求js類型-js引擎類型,需要做“boxed/unboxed(裝箱/解箱)”,在處理一次x=y+z這種計(jì)算,需要經(jīng)過的步驟如下。
從內(nèi)存,讀取x=y+z的操作符。
從內(nèi)存,讀取y,z。
檢查y,z類型,確定操作的行為。
unbox y,z。
執(zhí)行操作符的行為。
box x。
把x寫入內(nèi)存。
只有第5步驟是真正有效的操作,其他步驟都是為第5步驟做準(zhǔn)備/收尾,JAVASCRIPT的untyped特性很好用,但也為此付出了很大的性能代價(jià)。
JIT。
先看看JIT對untyped的優(yōu)化,在JIT下,執(zhí)行x=y+z流程。
從內(nèi)存,讀取x=y+z的操作符。
從內(nèi)存,讀取 y,z。
檢查y,z類型,確定操作的行為。
unbox y,z。
執(zhí)行 操作符 的行為。
box x。
把x寫入內(nèi)存。
其中1,2步驟由CPU負(fù)責(zé),7步驟JIT把結(jié)果保存在寄存器里。但可惜不是所有情況都能使用JIT,當(dāng)number+number,string+string 等等可以使用JIT,但特殊情況,如:number+undefined就不行了,只能走舊解析器。
新引擎還對“對象屬性”訪問做了優(yōu)化,解決方案叫inline caching,簡稱:IC。簡單的說,就是做cache。但如果當(dāng)list很大時(shí),這種方案反而影響效率。
Type-specializing JIT
Type-specializing JIT引擎用來處理typed類型(聲明類型)變量,但JAVASCRIPT都是untype類型的。
Type-specializing JIT的解決方案是:
先通過掃描,監(jiān)測類型。
通過編譯優(yōu)化(優(yōu)化對象不僅僅只是“類型”,還包括對JS代碼的優(yōu)化,但核心是類型優(yōu)化),生成類型變量。
再做后續(xù)計(jì)算。
Type-specializing JIT的執(zhí)行x=y+z流程:
從內(nèi)存,讀取x=y+z的操作符。
從內(nèi)存,讀取y,z。
檢查y,z類型,確定操作的行為。
unbox y,z。
執(zhí)行操作符的行為。
box x。
把x寫入內(nèi)存。
代價(jià)是:
前置的掃描類型
編譯優(yōu)化。
所以·Type-specializing JIT·的應(yīng)用是有選擇性,選擇使用這個(gè)引擎的場景包括:
熱點(diǎn)代碼。
通過啟發(fā)式算法估算出來的有價(jià)值的代碼。
另外,有2點(diǎn)也需要注意:
當(dāng)變量類型 發(fā)生變化時(shí),引擎有2種處理方式:
少量變更,重編譯,再執(zhí)行。
大量變更,交給JIT執(zhí)行。
數(shù)組,object properties, 閉包變量 不在優(yōu)化范疇之列。
with優(yōu)化
加快JavaScript裝入速度的工具:
Lab.js
借助LAB.js(裝入和阻止JavaScript),你就可以并行裝入JavaScript文件,加快總的裝入過程。此外,你還可以為需要裝入的腳本設(shè)置某個(gè)順序,那樣就能確保依賴關(guān)系的完整性。此外,開發(fā)者聲稱其網(wǎng)站上的速度提升了2倍。
使用適當(dāng)?shù)腃DN:
現(xiàn)在許多網(wǎng)頁使用內(nèi)容分發(fā)網(wǎng)絡(luò)(CDN)。它可以改進(jìn)你的緩存機(jī)制,因?yàn)槊總€(gè)人都可以使用它。它還能為你節(jié)省一些帶寬。你很容易使用ping檢測或使用Firebug調(diào)試那些服務(wù)器,以便搞清可以從哪些方面加快數(shù)據(jù)的速度。選擇CDN時(shí),要照顧到你網(wǎng)站那些訪客的位置。記得盡可能使用公共存儲庫。
網(wǎng)頁末尾裝入JavaScript:
也可以在頭部分放置需要裝入的一些JavaScript,但是前提是它以異步方式裝入。
異步裝入跟蹤代碼:
腳本加載與解析會阻塞HTML渲染,可以通過異步加載方式來避免渲染阻塞,步加載的方式很多,比較通用的方法如下。
var _gaq = _gaq || []; _gaq.push(["_setAccount", "UA-XXXXXXX-XX"]); _gaq.push(["_trackPageview"]); (function() { var ga = document.createElement("script"); ga.type = "text/JavaScript"; ga.async = true; ga.src = ("https:" == document.location.protocol ? "https://ssl" : "http://www") + ".google-analytics.com/ga.js"; var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(ga, s); })();或者
function loadjs (script_filename){ var script = document.createElement( "script" ); script.setAttribute( "type" , "text/javascript" ); script.setAttribute( "src" , script_filename); script.setAttribute( "id" , "script-id" ); scriptElement = document.getElementById( "script-id" ); if (scriptElement){ document.getElementsByTagName( "head" )[0].removeChild(scriptElement); } document.getElementsByTagName( "head" )[0].appendChild(script); } var script = "scripts/alert.js" ; loadjs(script);
把你的JavaScript打包成PNG文件
將JavaScript/css數(shù)據(jù)打包成PNG文件。之后進(jìn)行拆包,只要使用畫布API的getImageData()。可以在不縮小數(shù)據(jù)的情況下,多壓縮35%左右。而且是無損壓縮,對比較龐大的腳本來說,在圖片指向畫布、讀取像素的過程中,你會覺得有“一段”裝入時(shí)間。
設(shè)置Cache-Control和Expires頭
通過Cache-Control和Expires頭可以將腳本文件緩存在客戶端或者代理服務(wù)器上,可以減少腳本下載的時(shí)間。
Expires格式:
Expires = "Expires" ":" HTTP-date Expires: Thu, 01 Dec 1994 16:00:00 GMT Note: if a response includes a Cache-Control field with the max-age directive that directive overrides the Expires field.Cache-Control格式:
Cache-Control = "Cache-Control" ":" 1#cache-directive Cache-Control: public具體的標(biāo)準(zhǔn)定義可以參考http1.1中的定義,簡單來說Expires控制過期時(shí)間是多久,Cache-Control控制什么地方可以緩存 。
變量專題盡可能地少用with語句,因?yàn)樗鼤黾?b>with語句以外的數(shù)據(jù)的訪問代價(jià)。
避免使用with
> `with`語句將一個(gè)新的可變對象推入作用域鏈的頭部,函數(shù)的所有局部變量現(xiàn)在處于第二個(gè)作用域鏈對象中,從而使局部變量的訪問代價(jià)提高。var person = {
name: “Nicholas", age: 30}
function displayInfo() {var count = 5; with (person) { alert(name + " is " + age); alert( "count is " + count); }}
常規(guī)優(yōu)化
全局變量
當(dāng)一個(gè)變量被定義在全局作用域中,默認(rèn)情況下JAVASCRIPT引擎就不會將其回收銷毀。如此該變量就會一直存在于老生代堆內(nèi)存中,直到頁面被關(guān)閉。
全局變量缺點(diǎn)。
使變量不易被回收。
多人協(xié)作時(shí)容易產(chǎn)生混淆。
在作用域鏈中容易被干擾。
可以通過包裝函數(shù)來處理全局變量。
局部變量。
盡量選用局部變量而不是全局變量。
局部變量的訪問速度要比全局變量的訪問速度更快,因?yàn)槿肿兞科鋵?shí)是window對象的成員,而局部變量是放在函數(shù)的棧里的。
手工解除變量引用
在業(yè)務(wù)代碼中,一個(gè)變量已經(jīng)確定不再需要了,那么就可以手工解除變量引用,以使其被回收。
var data = { / some big data / };
// ...
data = null;
變量查找優(yōu)化。
變量聲明帶上var,如果聲明變量忘記了var,那么JAVASCRIPT引擎將會遍歷整個(gè)作用域查找這個(gè)變量,結(jié)果不管找到與否,都會造成性能損耗。
如果在上級作用域找到了這個(gè)變量,上級作用域變量的內(nèi)容將被無聲的改寫,導(dǎo)致莫名奇妙的錯(cuò)誤發(fā)生。
如果在上級作用域沒有找到該變量,這個(gè)變量將自動被聲明為全局變量,然而卻都找不到這個(gè)全局變量的定義。
慎用全局變量。
全局變量需要搜索更長的作用域鏈。
全局變量的生命周期比局部變量長,不利于內(nèi)存釋放。
過多的全局變量容易造成混淆,增大產(chǎn)生bug的可能性。
具有相同作用域變量通過一個(gè)var聲明。
jQuery.extend = jQuery.fn.extend = function () { var options, name, src, copy, copyIsArray, clone,target = arguments[0] || {}, i = 1, length = arguments.length, deep = false ; }
緩存重復(fù)使用的全局變量。
全局變量要比局部變量需要搜索的作用域長
重復(fù)調(diào)用的方法也可以通過局部緩存來提速
該項(xiàng)優(yōu)化在IE上體現(xiàn)比較明顯
var docElem = window.document.documentElement,
selector_hasDuplicate, matches = docElem.webkitMatchesSelector || docElem.mozMatchesSelector || docElem.oMatchesSelector ||docElem.msMatchesSelector, selector_sortOrder = function ( a, b ) { // Flag for duplicate removal if ( a === b ) { selector_hasDuplicate = true ; return 0; } }
善用回調(diào)。
除了使用閉包進(jìn)行內(nèi)部變量訪問,我們還可以使用現(xiàn)在十分流行的回調(diào)函數(shù)來進(jìn)行業(yè)務(wù)處理。
function getData(callback) { var data = "some big data"; callback(null, data); } getData(function(err, data) { console.log(data); });
回調(diào)函數(shù)是一種后續(xù)傳遞風(fēng)格(Continuation Passing Style, CPS)的技術(shù),這種風(fēng)格的程序編寫將函數(shù)的業(yè)務(wù)重點(diǎn)從返回值轉(zhuǎn)移到回調(diào)函數(shù)中去。而且其相比閉包的好處也有很多。
如果傳入的參數(shù)是基礎(chǔ)類型(如字符串、數(shù)值),回調(diào)函數(shù)中傳入的形參就會是復(fù)制值,業(yè)務(wù)代碼使用完畢以后,更容易被回收。
通過回調(diào),我們除了可以完成同步的請求外,還可以用在異步編程中,這也就是現(xiàn)在非常流行的一種編寫風(fēng)格。
回調(diào)函數(shù)自身通常也是臨時(shí)的匿名函數(shù),一旦請求函數(shù)執(zhí)行完畢,回調(diào)函數(shù)自身的引用就會被解除,自身也得到回收。
代碼壓縮
傳遞方法取代方法字符串
一些方法例如setTimeout()、setInterval(),接受字符串或者方法實(shí)例作為參數(shù)。直接傳遞方法對象作為參數(shù)來避免對字符串的二次解析。
傳遞方法
setTimeout(test, 1);
傳遞方法字符串
setTimeout("test()", 1);
使用原始操作代替方法調(diào)用
方法調(diào)用一般封裝了原始操作,在性能要求高的邏輯中,可以使用原始操作代替方法調(diào)用來提高性能。
原始操作
var min = a
方法實(shí)例
var min = Math.min(a, b);
定時(shí)器
如果針對的是不斷運(yùn)行的代碼,不應(yīng)該使用setTimeout,而應(yīng)該是用setInterval。setTimeout每次要重新設(shè)置一個(gè)定時(shí)器。
避免雙重解釋
當(dāng)JAVASCRIPT代碼想解析JAVASCRIPT代碼時(shí)就會存在雙重解釋懲罰,雙重解釋一般在使用eval函數(shù)、new Function構(gòu)造函數(shù)和setTimeout傳一個(gè)字符串時(shí)等情況下會遇到,如。
eval("alert("hello world");"); var sayHi = new Function("alert("hello world");"); setTimeout("alert("hello world");", 100);上述alert("hello world");語句包含在字符串中,即在JS代碼運(yùn)行的同時(shí)必須新啟運(yùn)一個(gè)解析器來解析新的代碼,而實(shí)例化一個(gè)新的解析器有很大的性能損耗。
我們看看下面的例子: var sum, num1 = 1, num2 = 2; /**效率低**/ for(var i = 0; i < 10000; i++){ var func = new Function("sum+=num1;num1+=num2;num2++;"); func(); //eval("sum+=num1;num1+=num2;num2++;"); } /**效率高**/ for(var i = 0; i < 10000; i++){ sum+=num1; num1+=num2; num2++; }第一種情況我們是使用了new Function來進(jìn)行雙重解釋,而第二種是避免了雙重解釋。
原生方法更快
只要有可能,使用原生方法而不是自已用JS重寫。原生方法是用諸如C/C++之類的編譯型語言寫出來的,要比JS的快多了。
最小化語句數(shù)
JS代碼中的語句數(shù)量也會影響所執(zhí)行的操作的速度,完成多個(gè)操作的單個(gè)語句要比完成單個(gè)操作的多個(gè)語句塊快。故要找出可以組合在一起的語句,以減來整體的執(zhí)行時(shí)間。這里列舉幾種模式
多個(gè)變量聲明
/不提倡/
var i = 1;
var j = "hello";
var arr = [1,2,3];
var now = new Date();
/提倡/
var i = 1,j = "hello", arr = [1,2,3], now = new Date();
插入迭代值
/不提倡/
var name = values[i];
i++;
/提倡/
var name = values[i++];
使用數(shù)組和對象字面量,避免使用構(gòu)造函數(shù)Array(),Object()
/不提倡/
var a = new Array();
a[0] = 1;
a[1] = "hello";
a[2] = 45;
var o = new Obejct();
o.name = "bill";
o.age = 13;
/提倡/
var a = [1, "hello", 45];
var o = {name : "bill", age : 13};
避免使用屬性訪問方法
JavaScript不需要屬性訪問方法,因?yàn)樗械膶傩远际峭獠靠梢姷摹?/p>
添加屬性訪問方法只是增加了一層重定向 ,對于訪問控制沒有意義。
使用屬性訪問方法示例function Car() {
this .m_tireSize = 17;
this .m_maxSpeed = 250;
this .GetTireSize = Car_get_tireSize;
this .SetTireSize = Car_put_tireSize;
}function Car_get_tireSize() {
return this .m_tireSize;
}function Car_put_tireSize(value) {
this .m_tireSize = value;
}
var ooCar = new Car();
var iTireSize = ooCar.GetTireSize();
ooCar.SetTireSize(iTireSize + 1);直接訪問屬性示例function Car() {
this .m_tireSize = 17;
this .m_maxSpeed = 250;
}
var perfCar = new Car();
var iTireSize = perfCar.m_tireSize;
perfCar.m_tireSize = iTireSize + 1;
減少使用元素位置操作
一般瀏覽器都會使用增量reflow的方式將需要reflow的操作積累到一定程度然后再一起觸發(fā),但是如果腳本中要獲取以下屬性,那么積累的reflow將會馬上執(zhí)行,已得到準(zhǔn)確的位置信息。
offsetLeft offsetTop offsetHeight offsetWidth scrollTop/Left/Width/Height clientTop/Left/Width/Height getComputedStyle()
代碼優(yōu)化
代碼壓縮工具
精簡代碼就是將代碼中的空格和注釋去除,也有更進(jìn)一步的會對變量名稱混淆、精簡。根據(jù)統(tǒng)計(jì)精簡后文件大小會平均減少21%,即使Gzip之后文件也會減少5%。
YUICompressor
Dean Edwards Packer
JSMin
GZip壓縮
GZip縮短在瀏覽器和服務(wù)器之間傳送數(shù)據(jù)的時(shí)間,縮短時(shí)間后得到標(biāo)題是Accept-Encoding: gzip,deflate的一個(gè)文件。不過這種壓縮方法同樣也有缺點(diǎn)。
它在服務(wù)器端和客戶端都要占用處理器資源(以便壓縮和解壓縮)。
占用磁盤空間。
Gzip通常可以減少70%網(wǎng)頁內(nèi)容的大小,包括腳本、樣式表、圖片等任何一個(gè)文本類型的響應(yīng),包括XML和JSON。Gzip比deflate更高效,主流服務(wù)器都有相應(yīng)的壓縮支持模塊。
Gzip的工作流程為
客戶端在請求Accept-Encoding中聲明可以支持Gzip。
服務(wù)器將請求文檔壓縮,并在Content-Encoding中聲明該回復(fù)為Gzip格式。
客戶端收到之后按照Gzip解壓縮。
Closure compiler
動畫優(yōu)化
優(yōu)化原則:
JS與其他語言不同在于它的執(zhí)行效率很大程度是取決于JS engine的效率。除了引擎實(shí)現(xiàn)的優(yōu)劣外,引擎自己也會為一些特殊的代碼模式采取一些優(yōu)化的策略。例如FF、Opera和Safari的JAVASCRIPT引擎,都對字符串的拼接運(yùn)算(+)做了特別優(yōu)化。所以應(yīng)該根據(jù)不同引擎進(jìn)行不同優(yōu)化。
而如果做跨瀏覽器的web編程,則最大的問題是在于IE6(JScript 5.6),因?yàn)樵诓淮騢otfix的情況下,JScript引擎的垃圾回收的bug,會導(dǎo)致其在真實(shí)應(yīng)用中的performance跟其他瀏覽器根本不在一個(gè)數(shù)量級上。因此在這種場合做優(yōu)化,實(shí)際上就是為JScript做優(yōu)化,所以第一原則就是只需要為IE6(未打補(bǔ)丁的JScript 5.6或更早版本)做優(yōu)化。
JS優(yōu)化總是出現(xiàn)在大規(guī)模循環(huán)的地方:
這倒不是說循環(huán)本身有性能問題,而是循環(huán)會迅速放大可能存在的性能問題,所以第二原則就是以大規(guī)模循環(huán)體為最主要優(yōu)化對象。以下的優(yōu)化原則,只在大規(guī)模循環(huán)中才有意義,在循環(huán)體之外做此類優(yōu)化基本上是沒有意義的。
目前絕大多數(shù)JS引擎都是解釋執(zhí)行的,而解釋執(zhí)行的情況下,在所有操作中,函數(shù)調(diào)用的效率是較低的。此外,過深的prototype繼承鏈或者多級引用也會降低效率。JScript中,10級引用的開銷大體是一次空函數(shù)調(diào)用開銷的1/2。這兩者的開銷都遠(yuǎn)遠(yuǎn)大于簡單操作(如四則運(yùn)算)。
盡量避免過多的引用層級和不必要的多次方法調(diào)用:
特別要注意的是,有些情況下看似是屬性訪問,實(shí)際上是方法調(diào)用。例如所有DOM的屬性,實(shí)際上都是方法。在遍歷一個(gè)NodeList的時(shí)候,循環(huán) 條件對于nodes.length的訪問,看似屬性讀取,實(shí)際上是等價(jià)于函數(shù)調(diào)用的。而且IE DOM的實(shí)現(xiàn)上,childNodes.length每次是要通過內(nèi)部遍歷重新計(jì)數(shù)的。(My god,但是這是真的!因?yàn)槲覝y過,childNodes.length的訪問時(shí)間與childNodes.length的值成正比!)這非常耗費(fèi)。所以 預(yù)先把nodes.length保存到j(luò)s變量,當(dāng)然可以提高遍歷的性能。同樣是函數(shù)調(diào)用,用戶自定義函數(shù)的效率又遠(yuǎn)遠(yuǎn)低于語言內(nèi)建函數(shù),因?yàn)楹笳呤菍σ姹镜胤椒ǖ陌b,而引擎通常是c,c++,java寫的。進(jìn)一步,同樣的功能,語言內(nèi)建構(gòu)造的開銷通常又比內(nèi)建函數(shù)調(diào)用要效率高,因?yàn)榍罢咴贘S代碼的parse階段就可以確定和優(yōu)化。
盡量使用語言本身的構(gòu)造和內(nèi)建函數(shù):
這里有一個(gè)例子是高性能的String.format方法。 String.format傳統(tǒng)的實(shí)現(xiàn)方式是用String.replace(regex, func),在pattern包含n個(gè)占位符(包括重復(fù)的)時(shí),自定義函數(shù)func就被調(diào)用n次。而這個(gè)高性能實(shí)現(xiàn)中,每次format調(diào)用所作的只是一次Array.join然后一次String.replace(regex, string)的操作,兩者都是引擎內(nèi)建方法,而不會有任何自定義函數(shù)調(diào)用。兩次內(nèi)建方法調(diào)用和n次的自定義方法調(diào)用,這就是性能上的差別。同樣是內(nèi)建特性,性能上也還是有差別的。例如在JScript中對于arguments的訪問性能就很差,幾乎趕上一次函數(shù)調(diào)用了。因此如果一個(gè) 可變參數(shù)的簡單函數(shù)成為性能瓶頸的時(shí)候,可以將其內(nèi)部做一些改變,不要訪問arguments,而是通過對參數(shù)的顯式判斷來處理,比如:
對象專題
動畫效果在缺少硬件加速支持的情況下反應(yīng)緩慢,例如手機(jī)客戶端。
特效應(yīng)該只在確實(shí)能改善用戶體驗(yàn)時(shí)才使用,而不應(yīng)用于炫耀或者彌補(bǔ)功能與可用性上的缺陷。
至少要給用戶一個(gè)選擇可以禁用動畫效果。
設(shè)置動畫元素為absolute或fixed。
position: static或position: relative元素應(yīng)用動畫效果會造成頻繁的reflow。
position: absolute或position: fixed的元素應(yīng)用動畫效果只需要repaint。
使用一個(gè)timer完成多個(gè)元素動畫。
setInterval和setTimeout是兩個(gè)常用的實(shí)現(xiàn)動畫的接口,用以間隔更新元素的風(fēng)格與布局。。
動畫效果的幀率最優(yōu)化的情況是使用一個(gè)timer完成多個(gè)對象的動畫效果,其原因在于多個(gè)timer的調(diào)用本身就會損耗一定性能。
setInterval(function() { animateFirst(""); }, 10); setInterval(function() { animateSecond(""); }, 10);使用同一個(gè)timer。
setInterval(function() { animateFirst(""); animateSecond(""); }, 10);以腳本為基礎(chǔ)的動畫,由瀏覽器控制動畫的更新頻率。
服務(wù)端優(yōu)化
減少不必要的對象創(chuàng)建:
創(chuàng)建對象本身對性能影響并不大,但由于JAVASCRIPT的垃圾回收調(diào)度算法,導(dǎo)致隨著對象個(gè)數(shù)的增加,性能會開始嚴(yán)重下降(復(fù)雜度O(n^2))。
如常見的字符串拼接問題,單純的多次創(chuàng)建字符串對象其實(shí)根本不是降低性能的主要原因,而是是在對象創(chuàng)建期間的無謂的垃圾回收的開銷。而Array.join的方式,不會創(chuàng)建中間字符串對象,因此就減少了垃圾回收的開銷。
復(fù)雜的JAVASCRIPT對象,其創(chuàng)建時(shí)時(shí)間和空間的開銷都很大,應(yīng)該盡量考慮采用緩存。
盡量作用JSON格式來創(chuàng)建對象,而不是var obj=new Object()方法。前者是直接復(fù)制,而后者需要調(diào)用構(gòu)造器。
對象查找
避免對象的嵌套查詢,因?yàn)?b>JAVASCRIPT的解釋性,a.b.c.d.e嵌套對象,需要進(jìn)行4次查詢,嵌套的對象成員會明顯影響性能。
如果出現(xiàn)嵌套對象,可以利用局部變量,把它放入一個(gè)臨時(shí)的地方進(jìn)行查詢。
對象屬性
訪問對象屬性消耗性能過程(JAVASCRIPT對象存儲)。
先從本地變量表找到對象。
然后遍歷屬性。
如果在當(dāng)前對象的屬性列表里沒找到。
繼續(xù)從prototype向上查找。
且不能直接索引,只能遍歷。
function f(obj) {
return obj.a + 1;}
類型轉(zhuǎn)換專題
避免404。
更改404錯(cuò)誤響應(yīng)頁面可以改進(jìn)用戶體驗(yàn),但是同樣也會浪費(fèi)服務(wù)器資源。
指向外部JAVASCRIPT的鏈接出現(xiàn)問題并返回404代碼。
這種加載會破壞并行加載。
其次瀏覽器會把試圖在返回的404響應(yīng)內(nèi)容中找到可能有用的部分當(dāng)作JavaScript代碼來執(zhí)行。
刪除重復(fù)的JAVASCRIPT和CSS。
重復(fù)調(diào)用腳本缺點(diǎn)。
增加額外的HTTP請求。
多次運(yùn)算也會浪費(fèi)時(shí)間。在IE和Firefox中不管腳本是否可緩存,它們都存在重復(fù)運(yùn)算JAVASCRIPT的問題。
ETags配置Entity標(biāo)簽。
ETags用來判斷瀏覽器緩存里的元素是否和原來服務(wù)器上的一致。
與last-modified date相比更靈活。
>如某個(gè)文件在1秒內(nèi)修改了10次,`ETags`可以綜合`Inode`(文件的索引節(jié)點(diǎn)`inode`數(shù)),`MTime`(修改時(shí)間)和`Size`來精準(zhǔn)的進(jìn)行判斷,避開`UNIX`記錄`MTime`只能精確到秒的問題。服務(wù)器集群使用,可取后兩個(gè)參數(shù)。使用`ETags`減少`Web`應(yīng)用帶寬和負(fù)載
權(quán)衡DNS查找次數(shù)
減少主機(jī)名可以節(jié)省響應(yīng)時(shí)間。但同時(shí)也會減少頁面中并行下載的數(shù)量。
IE瀏覽器在同一時(shí)刻只能從同一域名下載兩個(gè)文件。當(dāng)在一個(gè)頁面顯示多張圖片時(shí),IE用戶的圖片下載速度就會受到影響。
通過Keep-alive機(jī)制減少TCP連接。
通過CDN減少延時(shí)。
平行處理請求(參考BigPipe)。
通過合并文件或者Image Sprites減少HTTP請求。
減少重定向( HTTP 301和40x/50x)。
邏輯判斷優(yōu)化
把數(shù)字轉(zhuǎn)換成字符串。
應(yīng)用""+1,效率是最高。
性能上來說:""+字符串>String()>.toString()>new String()。
String()屬于內(nèi)部函數(shù),所以速度很快。
.toString()要查詢原型中的函數(shù),所以速度略慢。
new String()最慢。
浮點(diǎn)數(shù)轉(zhuǎn)換成整型。
錯(cuò)誤使用使用parseInt()。
parseInt()是用于將字符串轉(zhuǎn)換成數(shù)字,而不是浮點(diǎn)數(shù)和整型之間的轉(zhuǎn)換。
應(yīng)該使用Math.floor()或者Math.round()。
Math是內(nèi)部對象,所以Math.floor()其實(shí)并沒有多少查詢方法和調(diào)用的時(shí)間,速度是最快的。
內(nèi)存專題
switch語句。
若有一系列復(fù)雜的if-else語句,可以轉(zhuǎn)換成單個(gè)switch語句則可以得到更快的代碼,還可以通過將case語句按照最可能的到最不可能的順序進(jìn)行組織,來進(jìn)一步優(yōu)化。
事件優(yōu)化
JAVASCRIPT的內(nèi)存回收機(jī)制
以Google的V8引擎為例,在V8引擎中所有的JAVASCRIPT對象都是通過堆來進(jìn)行內(nèi)存分配的。當(dāng)我們在代碼中聲明變量并賦值時(shí),V8引擎就會在堆內(nèi)存中分配一部分給這個(gè)變量。如果已申請的內(nèi)存不足以存儲這個(gè)變量時(shí),V8引擎就會繼續(xù)申請內(nèi)存,直到堆的大小達(dá)到了V8引擎的內(nèi)存上限為止(默認(rèn)情況下,V8引擎的堆內(nèi)存的大小上限在64位系統(tǒng)中為1464MB,在32位系統(tǒng)中則為732MB)。
另外,V8引擎對堆內(nèi)存中的JAVASCRIPT對象進(jìn)行分代管理。
新生代。
新生代即存活周期較短的JAVASCRIPT對象,如臨時(shí)變量、字符串等
老生代。
老生代則為經(jīng)過多次垃圾回收仍然存活,存活周期較長的對象,如主控制器、服務(wù)器對象等。
垃圾回收算法。
垃圾回收算法一直是編程語言的研發(fā)中是否重要的??一環(huán),而V8引擎所使用的垃圾回收算法主要有以下幾種。
Scavange算法:通過復(fù)制的方式進(jìn)行內(nèi)存空間管理,主要用于新生代的內(nèi)存空間;
Mark-Sweep算法和Mark-Compact算法:通過標(biāo)記來對堆內(nèi)存進(jìn)行整理和回收,主要用于老生代對象的檢查和回收。
對象進(jìn)行回收。
引用。
當(dāng)函數(shù)執(zhí)行完畢時(shí),在函數(shù)內(nèi)部所聲明的對象不一定就會被銷毀。
引用(Reference)是JAVASCRIPT編程中十分重要的一個(gè)機(jī)制。
是指代碼對對象的訪問這一抽象關(guān)系,它與C/C++的指針有點(diǎn)相似,但并非同物。引用同時(shí)也是JAVASCRIPT引擎在進(jìn)行垃圾回收中最關(guān)鍵的一個(gè)機(jī)制。
var val = "hello world";
function foo() {
return function() {return val;};
}
global.bar = foo();
當(dāng)代碼執(zhí)行完畢時(shí),對象val和bar()并沒有被回收釋放,JAVASCRIPT代碼中,每個(gè)變量作為多帶帶一行而不做任何操作,JAVASCRIPT引擎都會認(rèn)為這是對對象的訪問行為,存在了對對象的引用。為了保證垃圾回收的行為不影響程序邏輯的運(yùn)行,JAVASCRIPT引擎不會把正在使用的對象進(jìn)行回收。所以判斷對象是否正在使用中的標(biāo)準(zhǔn),就是是否仍然存在對該對象的引用。
JAVASCRIPT的引用是可以進(jìn)行轉(zhuǎn)移的,那么就有可能出現(xiàn)某些引用被帶到了全局作用域,但事實(shí)上在業(yè)務(wù)邏輯里已經(jīng)不需要對其進(jìn)行訪問了,這個(gè)時(shí)候就應(yīng)該被回收,但是JAVASCRIPT引擎仍會認(rèn)為程序仍然需要它。
IE下閉包引起跨頁面內(nèi)存泄露。
JAVASCRIPT的內(nèi)存泄露處理
給DOM對象添加的屬性是一個(gè)對象的引用。
var MyObject = {};
document.getElementByIdx_x("myDiv").myProp = MyObject;解決方法:在window.onunload事件中寫上:document.getElementByIdx_x("myDiv").myProp = null;
DOM對象與JS對象相互引用。
function Encapsulator(element) {
this.elementReference = element; element.myProp = this;}
new Encapsulator(document.getElementByIdx_x("myDiv"));解決方法:在onunload事件中寫上:document.getElementByIdx_x("myDiv").myProp = null;
給DOM對象用attachEvent綁定事件。
function doClick() {}
element.attachEvent("onclick", doClick);解決方法:在onunload事件中寫上:element.detachEvent("onclick", doClick);
從外到內(nèi)執(zhí)行appendChild。這時(shí)即使調(diào)用removeChild也無法釋放。
var parentDiv = document.createElement_x("div");
var childDiv = document.createElement_x("div");
document.body.appendChild(parentDiv);
parentDiv.appendChild(childDiv);解決方法:從內(nèi)到外執(zhí)行appendChild:var parentDiv = document.createElement_x("div");
var childDiv = document.createElement_x("div");
parentDiv.appendChild(childDiv);
document.body.appendChild(parentDiv);
反復(fù)重寫同一個(gè)屬性會造成內(nèi)存大量占用(但關(guān)閉IE后內(nèi)存會被釋放)。
for(i = 0; i < 5000; i++) {
hostElement.text = "asdfasdfasdf";}
這種方式相當(dāng)于定義了5000個(gè)屬性,解決方法:無。
內(nèi)存不是緩存。
不要輕易將內(nèi)存當(dāng)作緩存使用。
如果是很重要的資源,請不要直接放在內(nèi)存中,或者制定過期機(jī)制,自動銷毀過期緩存。
CollectGarbage。
CollectGarbage是IE的一個(gè)特有屬性,用于釋放內(nèi)存的使用方法,將該變量或引用對象設(shè)置為null或delete然后在進(jìn)行釋放動作,在做CollectGarbage前,要必需清楚的兩個(gè)必備條件:(引用)。
一個(gè)對象在其生存的上下文環(huán)境之外,即會失效。
一個(gè)全局的對象在沒有被執(zhí)用(引用)的情況下,即會失效
使用事件代理
當(dāng)存在多個(gè)元素需要注冊事件時(shí),在每個(gè)元素上綁定事件本身就會對性能有一定損耗。
由于DOM Level2事件模 型中所有事件默認(rèn)會傳播到上層文檔對象,可以借助這個(gè)機(jī)制在上層元素注冊一個(gè)統(tǒng)一事件對不同子元素進(jìn)行相應(yīng)處理。
捕獲型事件先發(fā)生。兩種事件流會觸發(fā)DOM中的所有對象,從document對象開始,也在document對象結(jié)束。
同域跨域
當(dāng)需要使用數(shù)組時(shí),可使用JSON格式的語法
即直接使用如下語法定義數(shù)組:[parrm,param,param...],而不是采用new Array(parrm,param,param...)這種語法。使用JSON格式的語法是引擎直接解釋。而后者則需要調(diào)用Array的構(gòu)造器。
如果需要遍歷數(shù)組,應(yīng)該先緩存數(shù)組長度,將數(shù)組長度放入局部變量中,避免多次查詢數(shù)組長度。
根據(jù)字符串、數(shù)組的長度進(jìn)行循環(huán),而通常這個(gè)長度是不變的,比如每次查詢a.length,就要額外進(jìn)行一個(gè)操作,而預(yù)先把var len=a.length,則每次循環(huán)就少了一次查詢。
性能測試工具
避免跳轉(zhuǎn)
同域:注意避免反斜杠 “/” 的跳轉(zhuǎn);
跨域:使用Alias或者mod_rewirte建立CNAME(保存域名與域名之間關(guān)系的DNS記錄)
循環(huán)專題js性能優(yōu)化和內(nèi)存泄露問題及檢測分析工具
性能優(yōu)化ajax工具diviefirebug
[web性能分析工具YSlow]
performance性能評估打分,右擊箭頭可看到改進(jìn)建議。
stats緩存狀態(tài)分析,傳輸內(nèi)容分析。
components所有加載內(nèi)容分析,可以查看傳輸速度,找出頁面訪問慢的瓶頸。
tools可以查看js和css,并打印頁面評估報(bào)告。
內(nèi)存泄露檢測工具sIEve
sIEve是基于IE的內(nèi)存泄露檢測工具,需要下載運(yùn)行,可以查看dom孤立節(jié)點(diǎn)和內(nèi)存泄露及內(nèi)存使用情況。
列出當(dāng)前頁面內(nèi)所有dom節(jié)點(diǎn)的基本信息(html id style 等)
頁面內(nèi)所有dom節(jié)點(diǎn)的高級信息 (內(nèi)存占用,數(shù)量,節(jié)點(diǎn)的引用)
可以查找出頁面中的孤立節(jié)點(diǎn)
可以查找出頁面中的循環(huán)引用
可以查找出頁面中產(chǎn)生內(nèi)存泄露的節(jié)點(diǎn)
內(nèi)存泄露提示工具leak monitor
leak monitor在安裝后,當(dāng)離開一個(gè)頁面時(shí),比如關(guān)閉窗口,如果頁面有內(nèi)存泄露,會彈出一個(gè)文本框進(jìn)行即時(shí)提示。
代碼壓縮工具
YUI壓縮工具
Dean Edwards Packer
JSMin
Blink/Webkit瀏覽器
在Blink/Webkit瀏覽器中(Chrome, Safari, Opera),我們可以借助其中的Developer Tools的Profiles工具來對我們的程序進(jìn)行內(nèi)存檢查。
Developer Tools - Profiles
Node.js中的內(nèi)存檢查
在Node.js中,我們可以使用node-heapdump和node-memwatch模塊進(jìn)??行內(nèi)存檢查。
var heapdump = require("heapdump");
var fs = require("fs");
var path = require("path");
fs.writeFileSync(path.join(__dirname, "app.pid"), process.pid);在業(yè)務(wù)代碼中引入node-heapdump之后,我們需要在某個(gè)運(yùn)行時(shí)期,向Node.js進(jìn)程發(fā)送SIGUSR2信號,讓node-heapdump抓拍一份堆內(nèi)存的快照。
$ kill -USR2 (cat app.pid) 這樣在文件目錄下會有一個(gè)以`heapdump-. .heapsnapshot`格式命名的快照文件,我們可以使用瀏覽器的`Developer Tools`中的`Profiles`工具將其打開,并進(jìn)行檢查。 分析瀏覽器提供的Waterfall圖片來思考優(yōu)化入口。
新的測試手段(Navigation, Resource, 和User timing。
原型優(yōu)化
循環(huán)是一種常用的流程控制。
JAVASCRIPT提供了三種循環(huán)。
for(;;)。
推薦使用for循環(huán),如果循環(huán)變量遞增或遞減,不要多帶帶對循環(huán)變量賦值,而應(yīng)該使用嵌套的++或–-運(yùn)算符。
代碼的可讀性對于for循環(huán)的優(yōu)化。
用-=1。
從大到小的方式循環(huán)(這樣缺點(diǎn)是降低代碼的可讀性)。
/效率低/
var divs = document.getElementsByTagName("div");
for(var i = 0; i < divs.length; i++){...}
/效率高,適用于獲取DOM集合,如果純數(shù)組則兩種情況區(qū)別不到/
var divs = document.getElementsByTagName("div");
for(var i = 0, len = divs.length; i < len; i++){...}
/在IE6.0下,for(;;)循環(huán)在執(zhí)行中,第一種情況會每次都計(jì)算一下長度,而第二種情況卻是在開始的時(shí)候計(jì)算長度,并把其保存到一個(gè)變量中,所以其執(zhí)行效率要高點(diǎn),所以在我們使用for(;;)循環(huán)的時(shí)候,特別是需要計(jì)算長度的情況,我們應(yīng)該開始將其保存到一個(gè)變量中。/
while()。
for(;;)、while()循環(huán)的性能基本持平。
for(in)。
在這三種循環(huán)中for(in)內(nèi)部實(shí)現(xiàn)是構(gòu)造一個(gè)所有元素的列表,包括array繼承的屬性,然后再開始循環(huán),并且需要查詢hasOwnProperty。所以for(in)相對for(;;)循環(huán)性能要慢。
選擇正確的方法
避免不必要的屬性查找。
訪問變量或數(shù)組是O(1)操作。
訪問對象上的屬性是一個(gè)O(n)操作。
對象上的任何屬性查找都要比訪問變量或數(shù)組花費(fèi)更長時(shí)間,因?yàn)楸仨氃谠玩溨袑碛性撁Q的屬性進(jìn)行一次搜索,即屬性查找越多,執(zhí)行時(shí)間越長。所以針對需要多次用到對象屬性,應(yīng)將其存儲在局部變量。
優(yōu)化循環(huán)。
減值迭代。
大多數(shù)循環(huán)使用一個(gè)從0開始,增加到某個(gè)特定值的迭代器。在很多情況下,從最大值開始,在循環(huán)中不斷減值的迭代器更加有效。
簡化終止條件。
由于每次循環(huán)過程都會計(jì)算終止條件,故必須保證它盡可能快,即避免屬性查找或其它O(n)的操作。
簡化循環(huán)體。
循環(huán)體是執(zhí)行最多的,故要確保其被最大限度地優(yōu)化。確保沒有某些可以被很容易移出循環(huán)的密集計(jì)算。
使用后測試循環(huán)。
最常用的for和while循環(huán)都是前測試循環(huán),而如do-while循環(huán)可以避免最初終止條件的計(jì)算,因些計(jì)算更快。
for(var i = 0; i < values.length; i++) { process(values[i]); }優(yōu)化1:簡化終止條件
for(var i = 0, len = values.length; i < len; i++) { process(values[i]); }優(yōu)化2:使用后測試循環(huán)(注意:使用后測試循環(huán)需要確保要處理的值至少有一個(gè))
展開循環(huán)。
當(dāng)循環(huán)的次數(shù)確定時(shí),消除循環(huán)并使用多次函數(shù)調(diào)用往往更快。
當(dāng)循環(huán)的次數(shù)不確定時(shí),可以使用Duff裝置來優(yōu)化。
Duff裝置的基本概念是通過計(jì)算迭代的次數(shù)是否為8的倍數(shù)將一個(gè)循環(huán)展開為一系列語句。
// Jeff Greenberg for JS implementation of Duff"s Device // 假設(shè):values.length 0 function process(v) { alert(v); } var values = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17]; var iterations = Math.ceil(values.length / 8); var startAt = values.length % 8; var i = 0; do { switch(startAt) { case 0 : process(values[i++]); case 7 : process(values[i++]); case 6 : process(values[i++]); case 5 : process(values[i++]); case 4 : process(values[i++]); case 3 : process(values[i++]); case 2 : process(values[i++]); case 1 : process(values[i++]); } startAt = 0; }while(--iterations 0);如上展開循環(huán)可以提升大數(shù)據(jù)集的處理速度。接下來給出更快的Duff裝置技術(shù),將do-while循環(huán)分成2個(gè)多帶帶的循環(huán)。(注:這種方法幾乎比原始的Duff裝置實(shí)現(xiàn)快上40%。)
// Speed Up Your Site(New Riders, 2003) function process(v) { alert(v); } var values = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17]; var iterations = Math.floor(values.length / 8); var leftover = values.length % 8; var i = 0; if(leftover 0) { do { process(values[i++]); }while(--leftover 0); } do { process(values[i++]); process(values[i++]); process(values[i++]); process(values[i++]); process(values[i++]); process(values[i++]); process(values[i++]); process(values[i++]); }while(--iterations 0);針對大數(shù)據(jù)集使用展開循環(huán)可以節(jié)省很多時(shí)間,但對于小數(shù)據(jù)集,額外的開銷則可能得不償失。
避免在循環(huán)中使用try-catch。
try-catch-finally語句在catch語句被執(zhí)行的過程中會動態(tài)構(gòu)造變量插入到當(dāng)前域中,對性能有一定影響。
如果需要異常處理機(jī)制,可以將其放在循環(huán)外層使用。
循環(huán)中使用try-catch
for ( var i = 0; i < 200; i++) {
try {} catch (e) {}
}
循環(huán)外使用try-catch
try { for ( var i = 0; i < 200; i++) {} } catch (e) {}
避免遍歷大量元素:
避免對全局DOM元素進(jìn)行遍歷,如果parent已知可以指定parent在特定范圍查詢。
var elements = document.getElementsByTagName( "*" );
for (i = 0; i < elements.length; i++) {
if (elements[i].hasAttribute( "selected" )) {}
}如果已知元素存在于一個(gè)較小的范圍內(nèi),var elements = document.getElementById( "canvas" ).getElementsByTagName ( "*" );
for (i = 0; i < elements.length; i++) {
if (elements[i].hasAttribute( "selected" )) {}
}
運(yùn)算符專題
通過原型優(yōu)化方法定義。
如果一個(gè)方法類型將被頻繁構(gòu)造,通過方法原型從外面定義附加方法,從而避免方法的重復(fù)定義。
可以通過外部原型的構(gòu)造方式初始化值類型的變量定義。(這里強(qiáng)調(diào)值類型的原因是,引用類型如果在原型中定義,一個(gè)實(shí)例對引用類型的更改會影響到其他實(shí)例。)
這條規(guī)則中涉及到JAVASCRIPT中原型的概念,構(gòu)造函數(shù)都有一個(gè)prototype屬性,指向另一個(gè)對象。這個(gè)對象的所有屬性和方法,都會被構(gòu)造函數(shù)的實(shí)例繼承。可以把那些不變的屬性和方法,直接定義在prototype對象上。
可以通過對象實(shí)例訪問保存在原型中的值。
不能通過對象實(shí)例重寫原型中的值。
在實(shí)例中添加一個(gè)與實(shí)例原型同名屬性,那該屬性就會屏蔽原型中的屬性。
通過delete操作符可以刪除實(shí)例中的屬性。
重繪專題使用運(yùn)算符時(shí),盡量使用+=,-=、*=、=等運(yùn)算符號,而不是直接進(jìn)行賦值運(yùn)算。
位運(yùn)算。
當(dāng)進(jìn)行數(shù)學(xué)運(yùn)算時(shí)位運(yùn)算較快,位運(yùn)算操作要比任何布爾運(yùn)算或算數(shù)運(yùn)算快,如取模,邏輯與和邏輯或也可以考慮用位運(yùn)算來替換。
字符串專題
減少頁面的重繪。
減少頁面重繪雖然本質(zhì)不是JAVASCRIPT優(yōu)化,但重繪往往是由JAVASCRIPT引起的,而重繪的情況直接影響頁面性能。
var str = "
這是一個(gè)測試字符串";
/效率低/
var obj = document.getElementsByTagName("body");
for(var i = 0; i < 100; i++){obj.innerHTML += str + i;}
/效率高/
var obj = document.getElementsByTagName("body");
var arr = [];
for(var i = 0; i < 100; i++){arr[i] = str + i;}
obj.innerHTML = arr.join("");一般影響頁面重繪的不僅僅是innerHTML,如果改變元素的樣式,位置等情況都會觸發(fā)頁面重繪,所以在平時(shí)一定要注意這點(diǎn)。
使用HTML5和CSS3的一些新特性。
避免在HTML里面縮放圖片。
避免使用插件。
確保使用正確的字體大小。
決定當(dāng)前頁面是不是能被訪問。
作用域鏈和閉包優(yōu)化
對字符串進(jìn)行循環(huán)操作。
替換、查找等操作,使用正則表達(dá)式。
因?yàn)?b>JAVASCRIPT的循環(huán)速度較慢,而正則表達(dá)式的操作是用C寫成的API,性能比較好。
字符串的拼接。
字符串的拼接在我們開發(fā)中會經(jīng)常遇到,所以我把其放在首位,我們往往習(xí)慣的直接用+=的方式來拼接字符串,其實(shí)這種拼接的方式效率非常的低,我們可以用一種巧妙的方法來實(shí)現(xiàn)字符串的拼接,那就是利用數(shù)組的join方法,具體請看我整理的:Web前端開發(fā)規(guī)范文檔中的javaScript書寫規(guī)范倒數(shù)第三條目。
不過也有另一種說法,通常認(rèn)為需要用Array.join的方式,但是由于SpiderMonkey等引擎對字符串的“+”運(yùn)算做了優(yōu)化,結(jié)果使用Array.join的效率反而不如直接用“+”,但是如果考慮IE6,則其他瀏覽器上的這種效率的差別根本不值一提。具體怎么取舍,諸君自定。
作用域。
作用域(scope)是JAVASCRIPT編程中一個(gè)重要的運(yùn)行機(jī)制,在JAVASCRIPT同步和異步編程以及JAVASCRIPT內(nèi)存管理中起著至關(guān)重要的作用。
在JAVASCRIPT中,能形成作用域的有如下幾點(diǎn)。
函數(shù)的調(diào)用
with語句
with會創(chuàng)建自已的作用域,因此會增加其中執(zhí)行代碼的作用域的長度。
全局作用域。
以下代碼為例:
var foo = function() {
var local = {};
};
foo();
console.log(local); //=undefinedvar bar = function() {
local = {};
};
bar();
console.log(local); //={}/這里我們定義了foo()函數(shù)和bar()函數(shù),他們的意圖都是為了定義一個(gè)名為local的變量。在foo()函數(shù)中,我們使用var語句來聲明定義了一個(gè)local變量,而因?yàn)楹瘮?shù)體內(nèi)部會形成一個(gè)作用域,所以這個(gè)變量便被定義到該作用域中。而且foo()函數(shù)體內(nèi)并沒有做任何作用域延伸的處理,所以在該函數(shù)執(zhí)行完畢后,這個(gè)local變量也隨之被銷毀。而在外層作用域中則無法訪問到該變量。而在bar()函數(shù)內(nèi),local變量并沒有使用var語句進(jìn)行聲明,取而代之的是直接把local作為全局變量來定義。故外層作用域可以訪問到這個(gè)變量。/
local = {}; // 這里的定義等效于 global.local = {};
作用域鏈
在JAVASCRIPT編程中,會遇到多層函數(shù)嵌套的場景,這就是典型的作用域鏈的表示。
function foo() {
var val = "hello";
function bar() {function baz() { global.val = "world;" }; baz(); console.log(val); //=hello};
bar();
};
foo();/**在`JAVASCRIPT`中,變量標(biāo)識符的查找是從當(dāng)前作用域開始向外查找,直到全局作用域?yàn)橹埂K訿JAVASCRIPT`代碼中對變量的訪問只能向外進(jìn)行,而不能逆而行之。baz()函數(shù)的執(zhí)行在全局作用域中定義了一個(gè)全局變量val。而在bar()函數(shù)中,對val這一標(biāo)識符進(jìn)行訪問時(shí),按照從內(nèi)到外的查找原則:在bar函數(shù)的作用域中沒有找到,便到上一層,即foo()函數(shù)的作用域中查找。然而,使大家產(chǎn)生疑惑的關(guān)鍵就在這里:本次標(biāo)識符訪問在foo()函數(shù)的作用域中找到了符合的變量,便不會繼續(xù)向外查找,故在baz()函數(shù)中定義的全局變量val并沒有在本次變量訪問中產(chǎn)生影響。**/
減少作用域鏈上的查找次數(shù)
JAVASCRIPT代碼在執(zhí)行的時(shí)候,如果需要訪問一個(gè)變量或者一個(gè)函數(shù)的時(shí)候,它需要遍歷當(dāng)前執(zhí)行環(huán)境的作用域鏈,而遍歷是從這個(gè)作用域鏈的前端一級一級的向后遍歷,直到全局執(zhí)行環(huán)境。
/效率低/
for(var i = 0; i < 10000; i++){var but1 = document.getElementById("but1");}
/效率高/
/避免全局查找/
var doc = document;
for(var i = 0; i < 10000; i++){var but1 = doc.getElementById("but1");}
/上面代碼中,第二種情況是先把全局對象的變量放到函數(shù)里面先保存下來,然后直接訪問這個(gè)變量,而第一種情況是每次都遍歷作用域鏈,直到全局環(huán)境,我們看到第二種情況實(shí)際上只遍歷了一次,而第一種情況卻是每次都遍歷了,而且這種差別在多級作用域鏈和多個(gè)全局變量的情況下還會表現(xiàn)的非常明顯。在作用域鏈查找的次數(shù)是O(n)。通過創(chuàng)建一個(gè)指向document的局部變量,就可以通過限制一次全局查找來改進(jìn)這個(gè)函數(shù)的性能。/
閉包
JAVASCRIPT中的標(biāo)識符查找遵循從內(nèi)到外的原則。
function foo() { var local = "Hello"; return function() { return local; }; } var bar = foo(); console.log(bar()); //=Hello /**這里所展示的讓外層作用域訪問內(nèi)層作用域的技術(shù)便是閉包(Closure)。得益于高階函數(shù)的應(yīng)用,使foo()函數(shù)的作用域得到`延伸`。foo()函數(shù)返回了一個(gè)匿名函數(shù),該函數(shù)存在于foo()函數(shù)的作用域內(nèi),所以可以訪問到foo()函數(shù)作用域內(nèi)的local變量,并保存其引用。而因這個(gè)函數(shù)直接
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/85929.html
摘要:特意對前端學(xué)習(xí)資源做一個(gè)匯總,方便自己學(xué)習(xí)查閱參考,和好友們共同進(jìn)步。 特意對前端學(xué)習(xí)資源做一個(gè)匯總,方便自己學(xué)習(xí)查閱參考,和好友們共同進(jìn)步。 本以為自己收藏的站點(diǎn)多,可以很快搞定,沒想到一入?yún)R總深似海。還有很多不足&遺漏的地方,歡迎補(bǔ)充。有錯(cuò)誤的地方,還請斧正... 托管: welcome to git,歡迎交流,感謝star 有好友反應(yīng)和斧正,會及時(shí)更新,平時(shí)業(yè)務(wù)工作時(shí)也會不定期更...
摘要:為了可以頂下這個(gè)雷,特意買了高性能網(wǎng)站建設(shè)指南。規(guī)則七避免使用表達(dá)式原因表達(dá)式在你不知道得情況下執(zhí)行多次,嚴(yán)重影響前端性能。這也是會降低前端性能的。 最近要實(shí)現(xiàn)前端性能探測,可是對于一個(gè)剛?cè)肼殘龅奈襾碚f前端性能是個(gè)啥,我還是個(gè)只追求頁面展示效果的娃兒~。為了可以頂下這個(gè)雷,特意買了高性能網(wǎng)站建設(shè)指南。這本書真的不錯(cuò),強(qiáng)烈推薦看到本文的朋友看一下。 規(guī)則一 減少http請求數(shù) 原因:為什...
摘要:先來看一張系統(tǒng)前后端架構(gòu)模型圖。一種接口的約定本文用于定義一種統(tǒng)一的接口設(shè)計(jì)方案,希望具有參考價(jià)值。,和都是常見的軟件架構(gòu)設(shè)計(jì)模式,它通過分離關(guān)注點(diǎn)來改進(jìn)代碼的組織方式。 如何無痛降低 if else 面條代碼復(fù)雜度 相信不少同學(xué)在維護(hù)老項(xiàng)目時(shí),都遇到過在深深的 if else 之間糾纏的業(yè)務(wù)邏輯。面對這樣的一團(tuán)亂麻,簡單粗暴地繼續(xù)增量修改常常只會讓復(fù)雜度越來越高,可讀性越來越差,有沒...
摘要:前端每周清單年度總結(jié)與盤點(diǎn)在過去的八個(gè)月中,我?guī)缀踔蛔隽藘杉拢ぷ髋c整理前端每周清單。本文末尾我會附上清單線索來源與目前共期清單的地址,感謝每一位閱讀鼓勵(lì)過的朋友,希望你們能夠繼續(xù)支持未來的每周清單。 showImg(https://segmentfault.com/img/remote/1460000010890043); 前端每周清單年度總結(jié)與盤點(diǎn) 在過去的八個(gè)月中,我?guī)缀踔蛔隽?..
摘要:端優(yōu)談?wù)勱P(guān)于前端的緩存的問題我們都知道對頁面進(jìn)行緩存能夠有利于減少請求發(fā)送,從而達(dá)到對頁面的優(yōu)化。而作為一名有追求的前端,勢必要力所能及地優(yōu)化我們前端頁面的性能。這種方式主要解決了淺談前端中的過早優(yōu)化問題過早優(yōu)化是萬惡之源。 優(yōu)化向:單頁應(yīng)用多路由預(yù)渲染指南 Ajax 技術(shù)的出現(xiàn),讓我們的 Web 應(yīng)用能夠在不刷新的狀態(tài)下顯示不同頁面的內(nèi)容,這就是單頁應(yīng)用。在一個(gè)單頁應(yīng)用中,往往只有一...
閱讀 1512·2021-11-24 09:38
閱讀 3366·2021-11-18 10:02
閱讀 3253·2021-09-22 15:29
閱讀 2937·2021-09-22 15:15
閱讀 1037·2021-09-13 10:25
閱讀 1834·2021-08-17 10:13
閱讀 1971·2021-08-04 11:13
閱讀 1973·2019-08-30 15:54