首頁 web前端 js教程 web效能優化之javascript效能調優_javascript技巧

web效能優化之javascript效能調優_javascript技巧

May 16, 2016 pm 05:44 PM

JavaScript 是比較完善的前端開發語言,在現今的 web 開發中應用非常廣泛,尤其是對 Web 2.0 的應用。隨著 Web 2.0 越來越流行的今天,我們會發現:在我們的 web 應用專案中,會有大量的 JavaScript 程式碼,而且以後會越來越多。 JavaScript 作為一個解釋執行的語言,以及它的單線程機制,決定了效能問題是 JavaScript 的軟肋,也是 web 軟體工程師在寫 JavaScript 需要高度重視的問題,尤其是針對 Web 2.0 的應用。絕大多數 web 軟體工程師都或多或少的遇到所開發的 Web 2.0 應用的效能欠佳的問題,其主要原因就是 JavaScript 效能不足,瀏覽器負荷過重。但是,解決這種解釋執行並且單執行緒運作語言的效能問題也並非易事。這篇文章會著重介紹一些關於開發中JavaScript 效能調優的技巧和最佳實踐,同樣也會涉及到關於JavaScript 操作DOM 節點的效能調優的一些方法.

簡介
Web 開發中經常會遇到效能的問題,尤其是針對當今的Web2.0 應用。 JavaScript 是當今使用最廣泛的Web 開發語言,Web 應用的效能問題很大一部分都是由程式設計師寫的JavaScript 腳本效能不佳所造成的,裡麵包括了JavaScript 語言本身的效能問題,以及其與DOM 交互時的性能問題。本文主要來探討如何盡可能多的避免這類問題,從而最大限度的提高 Web 應用的效能。

JavaScript 效能調優
JavaScript 語言由於它的單執行緒和解釋執行的兩個特點,決定了它本身有很多地方有效能問題,所以可改進的地方有不少。

eval 的問題
比較下述程式碼:
清單1. eval 的問題

複製程式碼 程式碼如下:

var reference = {}, props = “p1”;
eval(“reference.” props “=5”)
var reference = {}, props = “p1”;
reference[props] = 5

有「eval」的程式碼比沒有「eval」的程式碼慢上100 倍以上。
主要原因是:JavaScript 程式碼在執行前會進行類似「預先編譯」的操作:首先會建立一個目前執行環境下的活動對象,並將那些以var 申明的變數設為活動對象的屬性,但是此時這些變數的賦值都是undefined,並將那些以function 定義的函數也加入為活動物件的屬性,而且它們的值正是函數的定義。但是,如果你使用了“eval”,則“eval”中的程式碼(實際上為字串)無法預先識別其上下文,無法事先解析和最佳化,即無法進行預編譯的操作。所以,其性能也會大幅降低。

Function 的用法
比較下述代碼:
清單2. function 的用法
程式碼如下:


var func1 = new Function(“return arguments[0] arguments[1]”);
func11 (10, 20);
var func2 = function(){ return arguments[0] arguments[1] };
func2(10, 20);


這裡類似之前提到的「eval」方法,這裡「func1」的效率會比「func2」的效率差很多,所以建議使用第二種方式。
函數的作用域鏈(scope chain):

JavaScript 程式碼解釋執行,在進入函數內部時,它會預先分析當前的變量,並將這些變數歸入不同的層級(level),一般情況下:
局部變數放入層級1(淺),全域變數放入層級2(深)。如果進入「with」或「try – catch」程式碼區塊,則會增加新的層級,即將「with」或「catch」裡的變數放入最淺層(層 1),並將先前的層級依序加深。
參考如下代碼: 清單3. 函數作用域鏈
代碼如下:


var myObj = … ..
… ..
function process(){
var images = document.getElementsByTagName("img"), wid = document.getElementsByTagName("input"),
combination = [];
for(var i = 0; i combination.push(combine(images[i] , widget[2*i]));
}
myObj.container.property1 = combination[0];
myObj.container.property2 = combination[combination.length-1];
}

這裡我們可以看到,“images”,“widget”,“combination”屬於局部變量,在層 1。 “document”,“myObj”屬於全域變量,在層 2。
變數所​​在的層越淺,存取(讀取或修改)速度越快,層越深,存取速度越慢。所以這裡對“images”,“widget”,“combination”的訪問速度比“document”,“myObj”要快一些。所以建議盡量使用局部變量,可見如下程式碼:
清單4. 使用局部變數
複製程式碼



複製程式碼



複製程式碼



複製程式碼


程式碼如下:


var myObj = … ..
… .. function process(){ var doc = document;
var images = doc. getElementsByTagName("img"),
widget = doc.getElementsByTagName("input"), combination = []; for(var i = 0; i combination.push(combine(images[i], widget[2*i])); } myObj.container.property1 = combination[0]; myObj.container.property2 = combination[ combination.length-1];
}


我們用局部變數“doc”取代全域變數“document”,這樣可以改善效能,尤其是對於大量使用全域變數的函數裡面。
再看如下代碼:

清單5. 慎用with





複製代碼


代碼如下:

var myObj = … ..
… ..
function process(){ var doc = document; var images = doc.getElementsByName(" img"),
widget = doc.getElementsByTagName("input"),
combination = []; for(var i = 0; i combination. push(combine(images[i], widget[2*i])); } with (myObj.container) { property1 = combination[0]; property2 = combination[combination .length-1];
}
}


加上「with」關鍵字,我們讓程式碼更加簡潔清晰了,但是這樣做效能會受影響。如同先前所說的,當我們進入「with」程式碼區塊時,「combination」便從原來的層 1 變到了層 2,這樣,效率會大打折扣。所以比較一下,還是用原來的程式碼:

清單6.改進。程式碼如下:


var myObj = … ..
… ..
function process(){
var doc = document;
var images = doc.getElementsByName(doc. "img"),
widget = doc.getElementsByTagName("input"),
combination = [];
for(var i = 0; i combination .push(combine(images[i], widget[2*i]));
} myObj.container.property1 = combination[0]; myObj.container.property2 = combination[combination. length-1];
}


複製程式碼


程式碼如下:


var myObj = … ..
… ..
function process(){
var doc = document;
var images = doc.getElementsByTagName("img"),
widget = doc.getElementsByTagName("input"),
combination = [];
for(var i = 0; i combination.push(combine(images[i], widget[2*i]));
}
var ctn = myObj.container;
ctn.property1 = combination[0]; ctn.property2 = combination[combination.length-1]; }


我們用局部變數來取代「myObj」的第2 層的「container ”對象。如果有大量的這種對物件深層屬性的訪問,可以參考以上方式提高效能。
字串(String)相關


複製程式碼
代碼如下: str = “str1”str2” str = “str1”str2” str = “str1”str2”
這是我們拼接字串常用的方式,但是這種方式會有一些臨時變數的建立和銷毀,影響效能,所以建議使用如下方式拼接:
清單9. 字串陣列方式拼接
複製程式碼 程式碼如下:

var str str_ >str_array.push(“str1”);
str_array.push(“str2”);
str = str_array.join(“”);

這裡我們利用陣列(array )的「join」方法實作字串的拼接,尤其是程式的舊版的Internet Explore(IE6)上運行時,會有非常明顯的效能上的改進。
當然,最新的瀏覽器(如火狐Firefox3 ,IE8 等等)對字串的拼接做了最佳化,我們也可以這樣寫:

清單10. 字串快速拼接
複製程式碼 程式碼如下:
str =“str1”
str =“str”

新的瀏覽器對「 =」做了最佳化,效能略快於陣列的「join」方法。在不久的將來更新版本瀏覽器可能會對「 」也會進行最佳化,所以那時我們可以直接寫入:str = “str1” “str2”。


隱式型別轉換 參考以下程式碼:

清單11. 隱式型別轉換




複製程式碼

程式碼如下:
var str = “12345678”, arr = [];
for(var i = 0; i arr.push( str.charAt(i)); } 這裡我們在每個循環時都會呼叫字串的“ charAt”方法,但由於我們是將常數“12345678”賦值給“str”,所以“str”這裡事實上並不是一個字串對象,當它每次調用“charAt”函數時,都會臨時構造值為“ 12345678”的字串對象,然後呼叫“charAt”方法,最後再釋放這個字串臨時對象。我們可以做一些改進: 清單12. 避免隱式類型轉換




複製代碼

代碼如下:

var str = new Stirng(“12345678”), arr = []; for(var i = 0; i arr.push( str.charAt(i));
}


這樣一來,變數「str」作為一個字串對象,就不會有這種隱式型別轉換的過程了,這樣一來,效率會顯著提高。
字串匹配
JavaScript 有 RegExp 對象,支援對字串的正規表示式匹配。是一個很好的工具,但是它的性能並不是非常理想。相反,字串物件(String)本身的一些基本方法的效率是非常高的,例如“substring”,“indexOf”,“charAt”等等,在我們需要用正則表達式匹配字串時,可以考慮一下: 是否能夠透過字串物件本身支援的基本方法解決問題。 是否可以透過「substring」來縮小需要用正規表示式的範圍。 這些方式都能夠有效的提高程序的效率。 關於正規表示式對象,還有一點要注意,參考如下程式碼: 清單13. 正規表示式




複製程式碼

程式碼如下:

for(var i = 0; i if(str_array[i].match (/^s*extras/)){
……………………




複製程式碼


程式碼如下:
var sExpr = /^s*extras/; for(var i = 0; i if(str_array[i].match(sExpr )){ …………………… } }
這樣就不會有臨時物件了。
setTimeout 和setInterval
「setTimeout」和「setInterval」這兩個函數可以接受字串變量,但是會帶來和之前談到的「eval」類似的效能問題,所以建議還是直接傳入函數對象本身。

利用提前退出
參考如下兩段代碼:
清單15. 利用提前退出



複製程式碼


程式碼如下:


// 程式碼1
var name = … .;
var source = … ;
if(source.match(/ …… /)){
……………………………
}
// 程式碼2
var name = … .;
var source = ... ; if(name.indexOf( … ) &&source.match(/ …… /)){ ……………………………
}


代碼2 多了一個對“name.indexOf( … )”的判斷,這使得程式每次走到這段時會先執行“indexOf”的判斷,再執行後面的“match” ,在「indexOf」比「match」更有效率的前提下,這樣做會減少「match」的執行次數,從而一定程度的提高效率。
----------------------------------------------- ---------------------------------

DOM 操作效能調優

JavaScript 的開發離不開DOM 的操作,所以對DOM 操作的效能調優在Web 開發中也是非常重要的。 Repaint 和 Reflow Repaint 也叫 Redraw,它指的是一種不會影響目前 DOM 的結構和佈局的一種重繪動作。如下動作會產生Repaint 動作:
不可見到可見(visibility 樣式屬性)
顏色或圖片變化(background, border-color, color 樣式屬性)
不改變頁面元素大小,形狀和位置,但改變其外觀的變化
Reflow 比起Repaint 來講就是一種更顯著的改變了。它主要發生在 DOM 樹被操作的時候,任何改變 DOM 的結構和佈局都會產生 Reflow。但當一個元素的 Reflow 操作發生時,它的所有父元素和子元素都會放生 Reflow,最後 Reflow 必然會導致 Repaint 的產生。舉例說明,以下動作會產生Repaint 動作:

瀏覽器視窗的變化
DOM 節點的新增刪除操作



複製程式碼



複製程式碼


複製程式碼
複製程式碼複製程式碼

複製程式碼



複製程式碼

var pDiv = document.createElement(“div”);
document.body.appendChild(pDiv);----- reflow
var cDiv1 = document.createElement(“ div”);
var cDiv2 = document.createElement(“div”); pDiv.appendChild(cDiv1);----- reflow pDiv.appendChild(cDiv2);----- reflow
這是我們常接觸的程式碼了,但這段程式碼會產生3 次reflow。再看以下程式碼: 清單17. 減少reflow

複製程式碼



複製程式碼



複製程式碼>

var pDiv = document.createElement(“div”);
var cDiv1 = document.createElement(“div”); var cDiv2 = document.createElement(“div”); var cDiv2 = document.createElement(“div”); 🎜>pDiv.appendChild(cDiv1); pDiv.appendChild(cDiv2); document.body.appendChild(pDiv);----- reflow 這裡便只有一次reflow,所以我們推薦這種DOM 節點操作的方式。 關於上述較少Reflow 操作的解決方案,還有一個可以參考的模式:清單18. 利用display 減少reflow 複製程式碼 程式碼如下: var pDiv = document.getElementById(“parent”); pDiv.style.display = “none”-🎜>pDiv.style.display = “none”-🎜>pDiv.style.display = “none”-🎜>pDiv.style.display = “none”-🎜>pDiv.style.display = “none”-🎜>pDiv.style.display = "none"- ---- reflow pDiv.appendChild(cDiv1); pDiv.appendChild(cDiv2); pDiv.appendChild(cDiv3); pDiv.appendChild(appendChild(cDiv3); pDiv.appendChild(cDiv4); appendChild(cDiv5); pDiv.style.width = “100px”; pDiv.style.height = “100px”; pDiv.style.display = “block”----- reflow
先隱藏pDiv,再顯示,這樣,隱藏和顯示之間的操作便不會產生任何的Reflow,提高了效率

特殊測量屬性和方法
DOM 元素裡面有一些特殊的測量屬性的存取和方法的調用,也會觸發Reflow,比較典型的就是「offsetWidth」屬性和「getComputedStyle」方法。
圖1. 特殊測量屬性和方法
web效能優化之javascript效能調優_javascript技巧 
這些測量屬性和方法大致有這些:
複製代碼 代碼如下:

offsetLeft
offsetTop
offsetHeight
HeoffsetWidth
scrollTop/Left/Width/Left/Width/Rft
getComputedStyle()
currentStyle(in IE))

這些屬性和方法的存取和調用,都會觸發Reflow 的產生,我們應該盡量減少對這些屬性和方法的訪問和調用,參考如下代碼:

清單19. 特殊測量屬性
複製代碼 代碼如下:
var pe = document.getElementById(“pos_element”);
var result = document.getElementById(“result_element”);
var pOffent.getElementById(“result_element”);
var pOffsetWidth = pepe. result.children[0].style.width = pOffsetWidth;
result.children[1].style.width = pOffsetWidth;
result.children[2].style.width = pOffsetWidth;

…………其他修改…………
這裡我們可以用臨時變數將「offsetWidth」的值快取起來,這樣就不用每次存取「offsetWidth」屬性。這種方式在循環裡面非常適用,可以大大提高效能。


樣式相關
我們一定常常看到如下的程式碼:
清單20. 樣式相關

複製程式碼 程式碼如下:
var sElement = document.getElementById(“pos_element”);
sElement.style.b =' 1px solid red '
sElement.style.backgroundColor = ' silver '
sElement.style.padding = ' 2px 3px '
sElement.style.marginLeft = ' 5px '





但可以看到,這裡的每一個樣式的改變,都會產生Reflow。需要減少這種情況的發生,我們可以這樣做:

複製程式碼


程式碼如下:


.class1 {
border: ' 1px solid red '
background-color: ' silver '
padding: ' 2px 3px '
margin-left: ' 5px '
} document.getElementById(“pos_element”).className = 'class1' ;


🎜>用class 取代style,可以將原有的所有Reflow 或Repaint 的次數都縮減到一個。


解決方案2

清單22. cssText 解決方案



> 程式碼如下: var sElement = document.getElementById(“pos_element”); var newStyle = ' border: 1px solid red; ' background-color; silver; '
' padding: 2px 3px; ' “margin-left: 5px;” sElement.style.cssText = newStyle; 一次設定所有樣式,也是減少Reflow提高性能的方法。 XPath 一個頁面上往往包含 1000 多頁面元素,在定位具體元素的時候,往往需要一定的時間。如果用 id 或 name 定位可能效率不會太慢,如果用元素的一些其他屬性(例如 className 等等)定位,可能效率有不理想了。有的可能只能透過遍歷所有元素(getElementsByTagName)然後過濾才能找到對應元素,這就更加低效了,這裡我們推薦使用 XPath 查找元素,這是許多瀏覽器本身支援的功能。 清單 23. XPath 解決方案 複製程式碼 程式碼

if(document.evaluate){
var tblHeaders = document.evaluate(“//body/div/table//th”);
var result = tblHeaders.iterateNext();
while(result) {
result.style.border = “1px dotted blue”;
result ………………
result = xpathResult.iterateNext();
}
}
} else{ //getElementsByTagName() ……
// 處理瀏覽器不支援XPath 的情況
………………………………
}


瀏覽器XPath 的搜尋引擎會優化搜尋效率,大幅縮短結果回傳時間。
HTMLCollection 物件

這是一類特殊的對象,它們有點像數組,但不完全是數組。下述方法的回傳值一般都是HTMLCollection 物件:
document.images, document.forms
getElementsByTagName()
getElementsByClassName()
這些HTMLCollection 物件並不是固定的值,而是固定的值,而是固定的值動態的結果。它們是一些比較特殊的查詢的回傳值,在下列情況下,它們會重新執行先前的查詢而得到新的回傳值(查詢結果),雖然多數情況下會和前一次或幾次的回傳值都一樣:
Length 屬性

具體的某個成員
所以,HTMLCollection 物件對這些屬性和成員的訪問,比起數組來慢很多。當然也有例外,Opera 和 Safari 對這種情況就處理的很好,不會有太大效能問題。
參考如下代碼: 清單24. HTMLConnection 對象
代碼如下:


var items = [“test1”, “test2”, “test3”, ……………… ];
for(var i = 0; i ………………………………
}
var items = document.getElementsByTagName(“div”);
for(var i = 0; i …………………………………… .
}


上述兩端代碼,下面的效率比起上面一段要慢很多,因為每個循環都會有「items.length」的觸發,也會導致「document.getElementsByTagName(..)」方法的再次調用,這便是效率便會大幅度下降的原因。我們可以這樣解決: 清單25. HTMLConnection 對象解決方案
代碼如下:


var items = document.getElementsByTagName(“div”);
var len = items.length
for(var i = 0; i …………………………………… .
}


這樣一來,效率基本上與普通數組一樣。
動態建立script 標籤
載入並執行一段JavaScript 腳本是需要一定時間的,在我們的程式中,有時候有些JavaScript 腳本被載入後基本上沒有被使用過(例如:腳本裡的函數從來沒有被調用等等)。載入這些腳本只會佔用 CPU 時間和增加記憶體消耗,降低 Web 應用的效能。所以推薦動態的載入 JavaScript 腳本文件,尤其是那些內容較多,消耗資源較大的腳本文件。 清單26. 建立script 標籤
程式碼
複製程式碼


程式碼

if(needXHR){
document.write(“
if(dojo.isIE){
document.write(“

--------- -------------------------------------------------- --------------------- 結束語 這篇文章介紹了Web 開發中關於效能方面需要注意的一些小細節,從JavaScript 本身著手,介紹了JavaScript 中需要避免的一些函數的使用和程式設計規則,例如eval 的弊端,function scope chain 以及String 的用法等等,也分享了一些比較推薦的做法,並擴展到JavaScript對DOM 操作的效能調優,例如利用Repaint 和Reflow 的機制,如何使用特殊測量屬性,樣式相關的效能調優以及HTMLCollection 物件的原理和使用小技巧。這些小細節我們可以在開發過程中盡量注意一下,以盡可能多的提升我們 Web 應用的效能。
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

<🎜>:泡泡膠模擬器無窮大 - 如何獲取和使用皇家鑰匙
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
北端:融合系統,解釋
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
Mandragora:巫婆樹的耳語 - 如何解鎖抓鉤
3 週前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

熱門話題

Java教學
1664
14
CakePHP 教程
1423
52
Laravel 教程
1321
25
PHP教程
1269
29
C# 教程
1249
24
JavaScript引擎:比較實施 JavaScript引擎:比較實施 Apr 13, 2025 am 12:05 AM

不同JavaScript引擎在解析和執行JavaScript代碼時,效果會有所不同,因為每個引擎的實現原理和優化策略各有差異。 1.詞法分析:將源碼轉換為詞法單元。 2.語法分析:生成抽象語法樹。 3.優化和編譯:通過JIT編譯器生成機器碼。 4.執行:運行機器碼。 V8引擎通過即時編譯和隱藏類優化,SpiderMonkey使用類型推斷系統,導致在相同代碼上的性能表現不同。

Python vs. JavaScript:學習曲線和易用性 Python vs. JavaScript:學習曲線和易用性 Apr 16, 2025 am 12:12 AM

Python更適合初學者,學習曲線平緩,語法簡潔;JavaScript適合前端開發,學習曲線較陡,語法靈活。 1.Python語法直觀,適用於數據科學和後端開發。 2.JavaScript靈活,廣泛用於前端和服務器端編程。

從C/C到JavaScript:所有工作方式 從C/C到JavaScript:所有工作方式 Apr 14, 2025 am 12:05 AM

從C/C 轉向JavaScript需要適應動態類型、垃圾回收和異步編程等特點。 1)C/C 是靜態類型語言,需手動管理內存,而JavaScript是動態類型,垃圾回收自動處理。 2)C/C 需編譯成機器碼,JavaScript則為解釋型語言。 3)JavaScript引入閉包、原型鍊和Promise等概念,增強了靈活性和異步編程能力。

JavaScript和Web:核心功能和用例 JavaScript和Web:核心功能和用例 Apr 18, 2025 am 12:19 AM

JavaScript在Web開發中的主要用途包括客戶端交互、表單驗證和異步通信。 1)通過DOM操作實現動態內容更新和用戶交互;2)在用戶提交數據前進行客戶端驗證,提高用戶體驗;3)通過AJAX技術實現與服務器的無刷新通信。

JavaScript在行動中:現實世界中的示例和項目 JavaScript在行動中:現實世界中的示例和項目 Apr 19, 2025 am 12:13 AM

JavaScript在現實世界中的應用包括前端和後端開發。 1)通過構建TODO列表應用展示前端應用,涉及DOM操作和事件處理。 2)通過Node.js和Express構建RESTfulAPI展示後端應用。

了解JavaScript引擎:實施詳細信息 了解JavaScript引擎:實施詳細信息 Apr 17, 2025 am 12:05 AM

理解JavaScript引擎內部工作原理對開發者重要,因為它能幫助編寫更高效的代碼並理解性能瓶頸和優化策略。 1)引擎的工作流程包括解析、編譯和執行三個階段;2)執行過程中,引擎會進行動態優化,如內聯緩存和隱藏類;3)最佳實踐包括避免全局變量、優化循環、使用const和let,以及避免過度使用閉包。

Python vs. JavaScript:社區,圖書館和資源 Python vs. JavaScript:社區,圖書館和資源 Apr 15, 2025 am 12:16 AM

Python和JavaScript在社區、庫和資源方面的對比各有優劣。 1)Python社區友好,適合初學者,但前端開發資源不如JavaScript豐富。 2)Python在數據科學和機器學習庫方面強大,JavaScript則在前端開發庫和框架上更勝一籌。 3)兩者的學習資源都豐富,但Python適合從官方文檔開始,JavaScript則以MDNWebDocs為佳。選擇應基於項目需求和個人興趣。

Python vs. JavaScript:開發環境和工具 Python vs. JavaScript:開發環境和工具 Apr 26, 2025 am 12:09 AM

Python和JavaScript在開發環境上的選擇都很重要。 1)Python的開發環境包括PyCharm、JupyterNotebook和Anaconda,適合數據科學和快速原型開發。 2)JavaScript的開發環境包括Node.js、VSCode和Webpack,適用於前端和後端開發。根據項目需求選擇合適的工具可以提高開發效率和項目成功率。

See all articles