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