web效能優化之javascript效能調優_javascript技巧
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"),
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"),
我們用局部變數“doc”取代全域變數“document”,這樣可以改善效能,尤其是對於大量使用全域變數的函數裡面。
再看如下代碼:
清單5. 慎用with
複製代碼
代碼如下:
… ..
function process(){ var doc = document; var images = doc.getElementsByName(" img"),
widget = doc.getElementsByTagName("input"),
加上「with」關鍵字,我們讓程式碼更加簡潔清晰了,但是這樣做效能會受影響。如同先前所說的,當我們進入「with」程式碼區塊時,「combination」便從原來的層 1 變到了層 2,這樣,效率會大打折扣。所以比較一下,還是用原來的程式碼:
清單6.改進。程式碼如下:
var myObj = … ..
… ..
function process(){
var doc = document;
var images = doc.getElementsByName(doc. "img"),
widget = doc.getElementsByTagName("input"),
combination = [];
} 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)相關
複製程式碼
這是我們拼接字串常用的方式,但是這種方式會有一些臨時變數的建立和銷毀,影響效能,所以建議使用如下方式拼接:
清單9. 字串陣列方式拼接
var str str_ >str_array.push(“str1”);
str_array.push(“str2”);
str = str_array.join(“”);
當然,最新的瀏覽器(如火狐Firefox3 ,IE8 等等)對字串的拼接做了最佳化,我們也可以這樣寫:
清單10. 字串快速拼接
str =“str”
隱式型別轉換 參考以下程式碼:
清單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」作為一個字串對象,就不會有這種隱式型別轉換的過程了,這樣一來,效率會顯著提高。
字串匹配
複製程式碼
for(var i = 0; i if(str_array[i].match (/^s*extras/)){
……………………
複製程式碼
程式碼如下:
這樣就不會有臨時物件了。
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 節點的新增刪除操作
複製程式碼
複製程式碼
複製程式碼
複製程式碼
複製程式碼
document.body.appendChild(pDiv);----- reflow
var cDiv1 = document.createElement(“ div”);
var cDiv2 = document.createElement(“div”); pDiv.appendChild(cDiv1);----- reflow pDiv.appendChild(cDiv2);----- reflow
複製程式碼
複製程式碼
複製程式碼>
var pDiv = document.createElement(“div”);
先隱藏pDiv,再顯示,這樣,隱藏和顯示之間的操作便不會產生任何的Reflow,提高了效率
特殊測量屬性和方法
DOM 元素裡面有一些特殊的測量屬性的存取和方法的調用,也會觸發Reflow,比較典型的就是「offsetWidth」屬性和「getComputedStyle」方法。
圖1. 特殊測量屬性和方法

這些測量屬性和方法大致有這些:
offsetLeft
offsetTop
offsetHeight
HeoffsetWidth
scrollTop/Left/Width/Left/Width/Rft
getComputedStyle()
currentStyle(in IE))
清單19. 特殊測量屬性
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;
…………其他修改…………
樣式相關
我們一定常常看到如下的程式碼:
清單20. 樣式相關
sElement.style.backgroundColor = ' silver '
sElement.style.padding = ' 2px 3px '
sElement.style.marginLeft = ' 5px '
但可以看到,這裡的每一個樣式的改變,都會產生Reflow。需要減少這種情況的發生,我們可以這樣做:
複製程式碼
程式碼如下:
.class1 {
border: ' 1px solid red '
padding: ' 2px 3px '
margin-left: ' 5px '
} document.getElementById(“pos_element”).className = 'class1' ;
🎜>用class 取代style,可以將原有的所有Reflow 或Repaint 的次數都縮減到一個。
解決方案2
:
清單22. cssText 解決方案
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(“
document.write(“
--------- -------------------------------------------------- --------------------- 結束語 這篇文章介紹了Web 開發中關於效能方面需要注意的一些小細節,從JavaScript 本身著手,介紹了JavaScript 中需要避免的一些函數的使用和程式設計規則,例如eval 的弊端,function scope chain 以及String 的用法等等,也分享了一些比較推薦的做法,並擴展到JavaScript對DOM 操作的效能調優,例如利用Repaint 和Reflow 的機制,如何使用特殊測量屬性,樣式相關的效能調優以及HTMLCollection 物件的原理和使用小技巧。這些小細節我們可以在開發過程中盡量注意一下,以盡可能多的提升我們 Web 應用的效能。

熱AI工具

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

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

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

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

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

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

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

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

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

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

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

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

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

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