2023-08-21 分類(lèi): 網(wǎng)站建設(shè)
百度權(quán)重查詢(xún) 站長(zhǎng)交易 友情鏈接交換 網(wǎng)站監(jiān)控 服務(wù)器監(jiān)控 seo監(jiān)控
許多javascript引擎,如Google的V8引擎(被Chrome和Node所用),是專(zhuān)門(mén)為需要快速執(zhí)行的大型javascript應(yīng)用所設(shè)計(jì)的。假如你是一個(gè)開(kāi)發(fā)者,并且關(guān)心內(nèi)存使用情況與頁(yè)面性能,你應(yīng)該了解用戶(hù)瀏覽器中的javascript引擎是如何運(yùn)作的。無(wú)論是V8,SpiderMonkey的(Firefox)的Carakan(Opera),Chakra(IE)或其他引擎,這樣做可以幫助你更好地優(yōu)化你的應(yīng)用程序。這并不是說(shuō)應(yīng)該專(zhuān)門(mén)為某一瀏覽器或引擎做優(yōu)化,萬(wàn)萬(wàn)別這么做。
但是,你應(yīng)該問(wèn)自己幾個(gè)問(wèn)題:
•在我的代碼里,是否可以使代碼更高效一些
•主流的javascript引擎都做了哪些優(yōu)化
•什么是引擎無(wú)法優(yōu)化的,垃圾回收器(GC)是否能回收我所期望的東西
加載快速的網(wǎng)站就像是一輛快速的跑車(chē),需要用到特殊定制的零件
編寫(xiě)高性能代碼時(shí)有一些常見(jiàn)的陷阱,在這篇文章中,我們將展示一些經(jīng)過(guò)驗(yàn)證的、更好的編寫(xiě)代碼體例。
那么,javascript在V8里是如何工作的?
假如你對(duì)JS引擎沒(méi)有較深的了解,開(kāi)發(fā)一個(gè)大型Web應(yīng)用也沒(méi)啥問(wèn)題,就好比會(huì)開(kāi)車(chē)的人也只是看過(guò)引擎蓋而沒(méi)有看過(guò)車(chē)蓋內(nèi)的引擎一樣。鑒于Chrome是我的瀏覽器,所以談一下它的javascript引擎。V8是由以下幾個(gè)核心部分組成:
•一個(gè)基本的編譯器,它會(huì)在代碼執(zhí)行前解析javascript代碼并生成本地機(jī)器碼,而不是執(zhí)行字節(jié)碼或簡(jiǎn)單地詮釋它。這些代碼好開(kāi)始并不是高度優(yōu)化的。
•V8將對(duì)象構(gòu)建為對(duì)象模型。在javascript中對(duì)象體現(xiàn)為關(guān)聯(lián)數(shù)組,但是在V8中對(duì)象被看作是隱藏的類(lèi),一個(gè)為了優(yōu)化查詢(xún)的內(nèi)部類(lèi)型系統(tǒng)。
•運(yùn)行時(shí)分析器監(jiān)視正在運(yùn)行的系統(tǒng),并標(biāo)識(shí)了“hot”的函數(shù)(例如花費(fèi)很長(zhǎng)時(shí)間運(yùn)行的代碼)。
•優(yōu)化編譯重視新編譯和優(yōu)化那些被運(yùn)行時(shí)分析器標(biāo)識(shí)為“hot”的代碼,并進(jìn)行“內(nèi)聯(lián)”等優(yōu)化(例如用被調(diào)用者的主體替代函數(shù)調(diào)用的位置)。
•V8支撐去優(yōu)化,這意味著優(yōu)化編譯器假如發(fā)現(xiàn)對(duì)于代碼優(yōu)化的假設(shè)過(guò)于樂(lè)觀(guān),它會(huì)舍棄優(yōu)化過(guò)的代碼。
•V8有個(gè)垃圾收集器,了解它是如何工作的和優(yōu)化javascript一樣主要。
垃圾回收
垃圾回收是內(nèi)存管理的一種形式,其實(shí)就是一個(gè)收集器的概念,嘗試回收不再被使用的對(duì)象所占用的內(nèi)存。在javascript這種垃圾回收語(yǔ)言中,應(yīng)用程序中仍在被引用的對(duì)象不會(huì)被消滅。
手動(dòng)消弭對(duì)象引用在大多數(shù)情況下是沒(méi)有需要的。通過(guò)簡(jiǎn)單地把變量放在需要它們的地方(理想情況下,盡可能是局部作用域,即它們被使用的函數(shù)里而不是函數(shù)外層),一切將運(yùn)作地很好。
垃圾回收器嘗試回收內(nèi)存
在javascript中,是不可能強(qiáng)制進(jìn)行垃圾回收的。你不應(yīng)該這么做,因?yàn)槔占^(guò)程是由運(yùn)行時(shí)控制的,它知道什么是好好的清理時(shí)機(jī)。
“消弭引用”的誤解
網(wǎng)上有許多關(guān)于javascript內(nèi)存回收的討論都談到delete這個(gè)關(guān)鍵字,雖然它可以被用來(lái)刪除對(duì)象(map)中的屬性(key),但有部分開(kāi)發(fā)者認(rèn)為它可以用來(lái)強(qiáng)制“消弭引用”。建議盡可能避免使用delete,在下面的例子中delete o.x 的弊大于利,因?yàn)樗淖兞薿的隱藏類(lèi),并使它成為一個(gè)"慢對(duì)象"。
var o = { x:1 };
delete o.x; // true
o.x; // undefined
你會(huì)很容易地在流行的JS庫(kù)中找到引用刪除——這是具有語(yǔ)言目的性的。這里需要注重的是避免在運(yùn)行時(shí)修改”hot”對(duì)象的結(jié)構(gòu)。javascript引擎可以檢測(cè)出這種“hot”的對(duì)象,并嘗試對(duì)其進(jìn)行優(yōu)化。假如對(duì)象在生命周期中其結(jié)構(gòu)沒(méi)有較大的改變,引擎將會(huì)更容易優(yōu)化對(duì)象,而delete操作現(xiàn)實(shí)上會(huì)觸發(fā)這種較大的結(jié)構(gòu)改變,因此不利于引擎的優(yōu)化。
對(duì)于null是如何工作也是有誤解的。將一個(gè)對(duì)象引用設(shè)置為null,并沒(méi)有使對(duì)象變“空”,只是將它的引用設(shè)置為空而已。使用o.x= null比使用delete會(huì)更好些,但可能也不是很需要。
var o = { x:1 };
o = null;
o; // null
o.x // TypeError
假如此引用是當(dāng)前對(duì)象的好后引用,那么該對(duì)象將被作為垃圾回收。假如此引用不是當(dāng)前對(duì)象的好后引用,則該對(duì)象是可訪(fǎng)問(wèn)的且不會(huì)被垃圾回收。
另外需要注重的是,全局變量在頁(yè)面的生命周期里是不被垃圾回收器清理的。無(wú)論頁(yè)面打開(kāi)多久,javascript運(yùn)行時(shí)全局對(duì)象作用域中的變量會(huì)一向存在。
var myGlobalNamespace = {};
全局對(duì)象只會(huì)在刷新頁(yè)面、導(dǎo)航到其他頁(yè)面、關(guān)閉標(biāo)簽頁(yè)或退出瀏覽器時(shí)才會(huì)被清理。函數(shù)作用域的變量將在超出作用域時(shí)被清理,即退出函數(shù)時(shí),已經(jīng)沒(méi)有任何引用,這樣的變量就被清理了。
經(jīng)驗(yàn)法則
為了使垃圾回收器盡早收集盡可能多的對(duì)象,不要hold著不再使用的對(duì)象。這里有幾件事需要記?。?/p>
•正如前面提到的,在合適的范圍內(nèi)使用變量是手動(dòng)消弭引用的更好選擇。即一個(gè)變量只在一個(gè)函數(shù)作用域中使用,就不要在全局作用域聲明它。這意味著更干凈省心的代碼。
•確保解綁那些不再需要的事件監(jiān)聽(tīng)器,尤其是那些即將被銷(xiāo)毀的DOM對(duì)象所綁定的事件監(jiān)聽(tīng)器。
•假如使用的數(shù)據(jù)緩存在本地,確保清理一下緩存或使用老化機(jī)制,以避免大量不被重用的數(shù)據(jù)被存儲(chǔ)。
函數(shù)
接下來(lái),我們談?wù)労瘮?shù)。正如我們已經(jīng)說(shuō)過(guò),垃圾收集的工作原理,是通過(guò)回收不再是訪(fǎng)問(wèn)的內(nèi)存塊(對(duì)象)。為了更好地說(shuō)明這一點(diǎn),這里有一些例子。
function foo(){
var bar = new LargeObject();
bar.someCall();
}
當(dāng)foo返回時(shí),bar指向的對(duì)象將會(huì)被垃圾收集器主動(dòng)回收,因?yàn)樗褯](méi)有任何存在的引用了。
對(duì)比一下:
function foo(){
var bar = new LargeObject();
bar.someCall();
return bar;
}
// somewhere else
var b = foo();
現(xiàn)在我們有一個(gè)引用指向bar對(duì)象,這樣bar對(duì)象的生存周期就從foo的調(diào)用一向持續(xù)到調(diào)用者指定別的變量b(或b超出范圍)。
閉包(CLOSURES)
當(dāng)你看到一個(gè)函數(shù),返回一個(gè)內(nèi)部函數(shù),該內(nèi)部函數(shù)將獲得范圍外的訪(fǎng)問(wèn)權(quán),即使在外部函數(shù)執(zhí)行之后。這是一個(gè)基本的閉包 —— 可以在特定的上下文中設(shè)置的變量的表達(dá)式。例如:
function sum (x){
function sumIt(y){
return x + y;
};
return sumIt;
}
// Usage
var sumA = sum(4);
var sumB = sumA(3);
console.log(sumB); // Returns 7
在sum調(diào)用上下文中生成的函數(shù)對(duì)象(sumIt)是無(wú)法被回收的,它被全局變量(sumA)所引用,并且可以通過(guò)sumA(n)調(diào)用。
讓我們來(lái)看看另外一個(gè)例子,這里我們可以訪(fǎng)問(wèn)變量largeStr嗎?
var a = function (){
var largeStr = new Array(1000000).join(‘x’);
return function (){
return largeStr;
};
}();
是的,我們可以通過(guò)a()訪(fǎng)問(wèn)largeStr,所以它沒(méi)有被回收。下面這個(gè)呢?
var a = function (){
var smallStr = ‘x’;
var largeStr = new Array(1000000).join(‘x’);
return function (n){
return smallStr;
};
}();
我們不能再訪(fǎng)問(wèn)largeStr了,它已經(jīng)是垃圾回收候選人了?!咀g者注:因?yàn)閘argeStr已不存在外部引用了】
準(zhǔn)時(shí)器
好糟的內(nèi)存泄露地方之一是在循環(huán)中,或者在setTimeout()/ setInterval()中,但這是相當(dāng)常見(jiàn)的。思考下面的例子:
var myObj = {
callMeMaybe:function (){
var myRef = this;
var val = setTimeout(function (){
console.log(‘Time is running out!’);
myRef.callMeMaybe();
},1000);
}
};
假如我們運(yùn)行myObj.callMeMaybe();來(lái)啟動(dòng)準(zhǔn)時(shí)器,可以看到控制臺(tái)每秒打印出“Time is running out!”。假如接著運(yùn)行myObj = null,準(zhǔn)時(shí)器依舊處于激活狀況。為了能夠持續(xù)執(zhí)行,閉包將myObj傳遞給setTimeout,這樣myObj是無(wú)法被回收的。相反,它引用到myObj的因?yàn)樗蹲搅薽yRef。這跟我們?yōu)榱吮3忠脤㈤]包傳給其他的函數(shù)是一樣的。
同樣值得牢記的是,setTimeout/setInterval調(diào)用(如函數(shù))中的引用,將需要執(zhí)行和完成,才可以被垃圾收集。
當(dāng)心性能陷阱
永遠(yuǎn)不要優(yōu)化代碼,直到你真正需要?,F(xiàn)在經(jīng)??梢钥吹揭恍┗鶞?zhǔn)測(cè)試,顯示N比M在V8中更為優(yōu)化,但是在模塊代碼或應(yīng)用中測(cè)試一下會(huì)發(fā)現(xiàn),這些優(yōu)化真正的效果比你期望的要小的多。
做的過(guò)多還不如什么都不做.
比如我們想要?jiǎng)?chuàng)建這樣一個(gè)模塊:
•需要一個(gè)本地的數(shù)據(jù)源包含數(shù)字ID
•繪制包含這些數(shù)據(jù)的表格
•添加事件處理程序,當(dāng)用戶(hù)點(diǎn)擊的任何單元格時(shí)切換單元格的css class
這個(gè)問(wèn)題有幾個(gè)不同的因素,雖然也很容易解決。我們?nèi)绾未鎯?chǔ)數(shù)據(jù),如何高效地繪制表格并且append到DOM中,如何更優(yōu)地處理表格事件?
面對(duì)這些問(wèn)題好開(kāi)始(無(wú)邪)的做法是使用對(duì)象存儲(chǔ)數(shù)據(jù)并放入數(shù)組中,使用jQuery遍歷數(shù)據(jù)繪制表格并append到DOM中,好后使用事件綁定我們期望地點(diǎn)擊行為。
注重:這不是你應(yīng)該做的
var moduleA = function (){
return {
data:dataArrayObject,
init:function (){
this.addTable();
this.addEvents();
},
addTable:function (){
for (var i = 0; i <rows; i++){
$tr = $(‘<tr></tr>’);
for (var j = 0; j <this.data.length; j++){
$tr.append(‘<td>’ + this.data[j][‘id’]+ ‘</td>’);
}
$tr.appendTo($tbody);
}
},
addEvents:function (){
$(‘table td’).on(‘click’,function (){
$(this).toggleClass(‘active’);
});
}
};
}();
這段代碼簡(jiǎn)單有用地完成了義務(wù)。
但在這種情況下,我們遍歷的數(shù)據(jù)只是本應(yīng)該簡(jiǎn)單地存放在數(shù)組中的數(shù)字型屬性ID。有趣的是,直接使用DocumentFragment和本地DOM方法比使用jQuery(以這種體例)來(lái)生成表格是更優(yōu)的選擇,當(dāng)然,事件代理比單獨(dú)綁定每個(gè)td具有更高的性能。
要注重雖然jQuery在內(nèi)部使用DocumentFragment,但是在我們的例子中,代碼在循環(huán)內(nèi)調(diào)用append并且這些調(diào)用涉及到一些其他的小知識(shí),因此在這里起到的優(yōu)化作用不大。希望這不會(huì)是一個(gè)痛點(diǎn),但請(qǐng)務(wù)必進(jìn)行基準(zhǔn)測(cè)試,以確保自己代碼ok。
對(duì)于我們的例子,上述的做法帶來(lái)了(期望的)性能提拔。事件代理對(duì)簡(jiǎn)單的綁定是一種改進(jìn),可選的DocumentFragment也起到了助推作用。
var moduleD = function (){
return {
data:dataArray,
init:function (){
this.addTable();
this.addEvents();
},
addTable:function (){
var td,tr;
var frag = document.createDocumentFragment();
var frag2 = document.createDocumentFragment();
for (var i = 0; i <rows; i++){
tr = document.createElement(‘tr’);
for (var j = 0; j <this.data.length; j++){
td = document.createElement(‘td’);
td.appendChild(document.createTextNode(this.data[j]));
frag2.appendChild(td);
}
tr.appendChild(frag2);
frag.appendChild(tr);
}
tbody.appendChild(frag);
},
addEvents:function (){
$(‘table’).on(‘click’,‘td’,function (){
$(this).toggleClass(‘active’);
});
}
};
}();
網(wǎng)站標(biāo)題:編寫(xiě)高性能javascript
路徑分享:http://aaarwkj.com/news34/277584.html成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供微信公眾號(hào)、外貿(mào)建站、品牌網(wǎng)站建設(shè)、手機(jī)網(wǎng)站建設(shè)、移動(dòng)網(wǎng)站建設(shè)、App開(kāi)發(fā)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶(hù)投稿、用戶(hù)轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話(huà):028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容