小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

【譯】編寫(xiě)高性能JavaScript

 quasiceo 2016-07-25

【譯】編寫(xiě)高性能JavaScript

作者: Addy Osmani  來(lái)源: AlloyTeam  發(fā)布時(shí)間: 2014-08-12 09:22  閱讀: 10822 次  推薦: 33   原文鏈接   [收藏]  

  英文鏈接:Writing Fast, Memory-Efficient JavaScript

  很多JavaScript引擎,如Google的V8引擎(被Chrome和Node所用),是專門(mén)為需要快速執(zhí)行的大型JavaScript應(yīng)用所設(shè)計(jì)的。如果你是一個(gè)開(kāi)發(fā)者,并且關(guān)心內(nèi)存使用情況與頁(yè)面性能,你應(yīng)該了解用戶瀏覽器中的JavaScript引擎是如何運(yùn)作的。無(wú)論是V8,SpiderMonkey的(Firefox)的Carakan(Opera),Chakra(IE)或其他引擎,這樣做可以幫助你更好地優(yōu)化你的應(yīng)用程序。這并不是說(shuō)應(yīng)該專門(mén)為某一瀏覽器或引擎做優(yōu)化,千萬(wàn)別這么做。

  但是,你應(yīng)該問(wèn)自己幾個(gè)問(wèn)題:

  • 在我的代碼里,是否可以使代碼更高效一些
  • 主流的JavaScript引擎都做了哪些優(yōu)化
  • 什么是引擎無(wú)法優(yōu)化的,垃圾回收器(GC)是否能回收我所期望的東西

加載快速的網(wǎng)站就像是一輛快速的跑車(chē),需要用到特別定制的零件. 圖片來(lái)源: dHybridcars.

  編寫(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ì)象被看作是隱藏的類,一個(gè)為了優(yōu)化查詢的內(nè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è)觀,它會(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)存. 圖片來(lái)源: Valtteri M?ki.

  在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)樗淖兞薿的隱藏類,并使它成為一個(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操作實(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ì)象是可訪問(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)器,尤其是那些即將被銷毀的DOM對(duì)象所綁定的事件監(jiān)聽(tīng)器。
  • 如果使用的數(shù)據(jù)緩存在本地,確保清理一下緩存或使用老化機(jī)制,以避免大量不被重用的數(shù)據(jù)被存儲(chǔ)。

  函數(shù)

  接下來(lái),我們談?wù)労瘮?shù)。正如我們已經(jīng)說(shuō)過(guò),垃圾收集的工作原理,是通過(guò)回收不再是訪問(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ù)將獲得范圍外的訪問(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è)例子,這里我們可以訪問(wèn)變量largeStr嗎?

var a = function () {
    var largeStr = new Array(1000000).join('x');
    return function () {
        return largeStr;
    };
}();

  是的,我們可以通過(guò)a()訪問(wèn)largeStr,所以它沒(méi)有被回收。下面這個(gè)呢?

var a = function () {
    var smallStr = 'x';
    var largeStr = new Array(1000000).join('x');
    return function (n) {
        return smallStr;
    };
}();

  我們不能再訪問(wèn)largeStr了,它已經(jīng)是垃圾回收候選人了?!咀g者注:因?yàn)閘argeStr已不存在外部引用了】

  定時(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)定時(shí)器,可以看到控制臺(tái)每秒打印出“Time is running out!”。如果接著運(yùn)行myObj = null,定時(shí)器依舊處于激活狀態(tài)。為了能夠持續(xù)執(zhí)行,閉包將myObj傳遞給setTimeout,這樣myObj是無(wú)法被回收的。相反,它引用到myObj的因?yàn)樗东@了myRef。這跟我們?yōu)榱吮3忠脤㈤]包傳給其他的函數(shù)是一樣的。

  同樣值得牢記的是,setTimeout/setInterval調(diào)用(如函數(shù))中的引用,將需要執(zhí)行和完成,才可以被垃圾收集。

  當(dāng)心性能陷阱

  永遠(yuǎn)不要優(yōu)化代碼,直到你真正需要。現(xiàn)在經(jīng)??梢钥吹揭恍┗鶞?zhǔn)測(cè)試,顯示N比M在V8中更為優(yōu)化,但是在模塊代碼或應(yīng)用中測(cè)試一下會(huì)發(fā)現(xiàn),這些優(yōu)化真正的效果比你期望的要小的多。

做的過(guò)多還不如什么都不做. 圖片來(lái)源: Tim Sheerman-Chase.

  比如我們想要?jiǎng)?chuàng)建這樣一個(gè)模塊:

  • 需要一個(gè)本地的數(shù)據(jù)源包含數(shù)字ID
  • 繪制包含這些數(shù)據(jù)的表格
  • 添加事件處理程序,當(dāng)用戶點(diǎn)擊的任何單元格時(shí)切換單元格的css class

  這個(gè)問(wèn)題有幾個(gè)不同的因素,雖然也很容易解決。我們?nèi)绾未鎯?chǔ)數(shù)據(jù),如何高效地繪制表格并且append到DOM中,如何更優(yōu)地處理表格事件?

  面對(duì)這些問(wèn)題最開(kāi)始(天真)的做法是使用對(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');
            });
        }
 
    };
 
}();

  接下來(lái)看看其他提升性能的方式。你也許曾經(jīng)在哪讀到過(guò)使用原型模式比模塊模式更優(yōu),或聽(tīng)說(shuō)過(guò)使用JS模版框架性能更好。有時(shí)的確如此,不過(guò)使用它們其實(shí)是為了代碼更具可讀性。對(duì)了,還有預(yù)編譯!讓我們看看在實(shí)踐中表現(xiàn)的如何?

moduleG = function () {};
 
moduleG.prototype.data = dataArray;
moduleG.prototype.init = function () {
    this.addTable();
    this.addEvents();
};
moduleG.prototype.addTable = function () {
    var template = _.template($('#template').text());
    var html = template({'data' : this.data});
    $tbody.append(html);
};
moduleG.prototype.addEvents = function () {
   $('table').on('click', 'td', function () {
       $(this).toggleClass('active');
   });
};
 
var modG = new moduleG();

  事實(shí)證明,在這種情況下的帶來(lái)的性能提升可以忽略不計(jì)。模板和原型的選擇并沒(méi)有真正提供更多的東西。也就是說(shuō),性能并不是開(kāi)發(fā)者使用它們的原因,給代碼帶來(lái)的可讀性、繼承模型和可維護(hù)性才是真正的原因。

  更復(fù)雜的問(wèn)題包括高效地在canvas上繪制圖片和操作帶或不帶類型數(shù)組的像素?cái)?shù)據(jù)。

  在將一些方法用在你自己的應(yīng)用之前,一定要多了解這些方案的基準(zhǔn)測(cè)試。也許有人還記得JS模版的shoot-off隨后的擴(kuò)展版。你要搞清楚基準(zhǔn)測(cè)試不是存在于你看不到的那些虛擬應(yīng)用,而是應(yīng)該在你的實(shí)際代碼中去測(cè)試帶來(lái)的優(yōu)化。

  V8優(yōu)化技巧

  詳細(xì)介紹了每個(gè)V8引擎的優(yōu)化點(diǎn)在本文討論范圍之外,當(dāng)然這里也有許多值得一提的技巧。記住這些技巧你就能減少那些性能低下的代碼了。

  • 特定模式可以使V8擺脫優(yōu)化的困境,比如說(shuō)try-catch。欲了解更多有關(guān)哪些函數(shù)能或不能進(jìn)行優(yōu)化,你可以在V8的腳本工具d8中使用–trace-opt file.js命令。
  • 如果你關(guān)心速度,盡量使你的函數(shù)職責(zé)單一,即確保變量(包括屬性,數(shù)組,函數(shù)參數(shù))只使用相同隱藏類包含的對(duì)象。舉個(gè)例子,別這么干:
function add(x, y) {
   return x+y;
}
 
add(1, 2);
add('a','b');
add(my_custom_object, undefined);
  • 不要加載未初始化或已刪除的元素。如果這么做也不會(huì)出現(xiàn)什么錯(cuò)誤,但是這樣會(huì)使速度變慢。
  • 不要使函數(shù)體過(guò)大,這樣會(huì)使得優(yōu)化更加困難。

  更多內(nèi)容可以去看Daniel Clifford在Google I/O的分享 Breaking the JavaScript Speed Limit with V8。 Optimizing For V8 — A Series也非常值得一讀。

  對(duì)象VS數(shù)組:我應(yīng)該用哪個(gè)?

  • 如果你想存儲(chǔ)一串?dāng)?shù)字,或者一些相同類型的對(duì)象,使用一個(gè)數(shù)組。
  • 如果你語(yǔ)義上需要的是一堆的對(duì)象的屬性(不同類型的),使用一個(gè)對(duì)象和屬性。這在內(nèi)存方面非常高效,速度也相當(dāng)快。
  • 整數(shù)索引的元素,無(wú)論存儲(chǔ)在一個(gè)數(shù)組或?qū)ο笾校家?a href="http:///performance-of-array-vs-object/3" target="_blank">比遍歷對(duì)象的屬性快得多。
  • 對(duì)象的屬性比較復(fù)雜:它們可以被setter們創(chuàng)建,具有不同的枚舉性和可寫(xiě)性。數(shù)組中則不具有如此的定制性,而只存在有和無(wú)這兩種狀態(tài)。在引擎層面,這允許更多存儲(chǔ)結(jié)構(gòu)方面的優(yōu)化。特別是當(dāng)數(shù)組中存在數(shù)字時(shí),例如當(dāng)你需要容器時(shí),不用定義具有x,y,z屬性的類,而只用數(shù)組就可以了。

  JavaScript中對(duì)象和數(shù)組之間只有一個(gè)的主要區(qū)別,那就是數(shù)組神奇的length屬性。如果你自己來(lái)維護(hù)這個(gè)屬性,那么V8中對(duì)象和數(shù)組的速度是一樣快的。

  使用對(duì)象時(shí)的技巧

  • 使用一個(gè)構(gòu)造函數(shù)來(lái)創(chuàng)建對(duì)象。這將確保它創(chuàng)建的所有對(duì)象具有相同的隱藏類,并有助于避免更改這些類。作為一個(gè)額外的好處,它也略快于Object.create()
  • 你的應(yīng)用中,對(duì)于使用不同類型的對(duì)象和其復(fù)雜度(在合理的范圍內(nèi):長(zhǎng)原型鏈往往是有害的,呈現(xiàn)只有一個(gè)極少數(shù)屬性的對(duì)象比大對(duì)象會(huì)快一點(diǎn))是有沒(méi)限制的。對(duì)于“hot”對(duì)象,盡量保持短原型鏈,并且少屬性。

  對(duì)象克隆

  對(duì)于應(yīng)用程序開(kāi)發(fā)人員,對(duì)象克隆是一個(gè)常見(jiàn)的問(wèn)題。雖然各種基準(zhǔn)測(cè)試可以證明V8對(duì)這個(gè)問(wèn)題處理得很好,但仍要小心。復(fù)制大的東西通常是較慢的——不要這么做。JS中的for..in循環(huán)尤其糟糕,因?yàn)樗兄鴲耗О愕囊?guī)范,并且無(wú)論是在哪個(gè)引擎中,都可能永遠(yuǎn)不會(huì)比任何對(duì)象快。

  當(dāng)你一定要在關(guān)鍵性能代碼路徑上復(fù)制對(duì)象時(shí),使用數(shù)組或一個(gè)自定義的“拷貝構(gòu)造函數(shù)”功能明確地復(fù)制每個(gè)屬性。這可能是最快的方式:

function clone(original) {
  this.foo = original.foo;
  this.bar = original.bar;
}
var copy = new clone(original);

  模塊模式中緩存函數(shù)

  使用模塊模式時(shí)緩存函數(shù),可能會(huì)導(dǎo)致性能方面的提升。參閱下面的例子,因?yàn)樗偸莿?chuàng)建成員函數(shù)的新副本,你看到的變化可能會(huì)比較慢。

  另外請(qǐng)注意,使用這種方法明顯更優(yōu),不僅僅是依靠原型模式(經(jīng)過(guò)jsPerf測(cè)試確認(rèn))。

使用模塊模式或原型模式時(shí)的性能提升

  這是一個(gè)原型模式與模塊模式的性能對(duì)比測(cè)試

  // Prototypal pattern
  Klass1 = function () {}
  Klass1.prototype.foo = function () {
      log('foo');
  }
  Klass1.prototype.bar = function () {
      log('bar');
  }
 
  // Module pattern
  Klass2 = function () {
      var foo = function () {
          log('foo');
      },
      bar = function () {
          log('bar');
      };
 
      return {
          foo: foo,
          bar: bar
      }
  }
 
  // Module pattern with cached functions
  var FooFunction = function () {
      log('foo');
  };
  var BarFunction = function () {
      log('bar');
  };
 
  Klass3 = function () {
      return {
          foo: FooFunction,
          bar: BarFunction
      }
  }
 
  // Iteration tests
 
  // Prototypal
  var i = 1000,
      objs = [];
  while (i--) {
      var o = new Klass1()
      objs.push(new Klass1());
      o.bar;
      o.foo;
  }
 
  // Module pattern
  var i = 1000,
      objs = [];
  while (i--) {
      var o = Klass2()
      objs.push(Klass2());
      o.bar;
      o.foo;
  }
 
  // Module pattern with cached functions
  var i = 1000,
      objs = [];
  while (i--) {
      var o = Klass3()
      objs.push(Klass3());
      o.bar;
      o.foo;
  }
// See the test for full details

  使用數(shù)組時(shí)的技巧

  接下來(lái)說(shuō)說(shuō)數(shù)組相關(guān)的技巧。在一般情況下,不要?jiǎng)h除數(shù)組元素,這樣將使數(shù)組過(guò)渡到較慢的內(nèi)部表示。當(dāng)索引變得稀疏,V8將會(huì)使元素轉(zhuǎn)為更慢的字典模式。

  數(shù)組字面量

  數(shù)組字面量非常有用,它可以暗示VM數(shù)組的大小和類型。它通常用在體積不大的數(shù)組中。

// Here V8 can see that you want a 4-element array containing numbers:
var a = [1, 2, 3, 4];
 
// Don't do this:
a = []; // Here V8 knows nothing about the array
for(var i = 1; i <= 4; i++) {
     a.push(i);
}

  存儲(chǔ)單一類型VS多類型

  將混合類型(比如數(shù)字、字符串、undefined、true/false)的數(shù)據(jù)存在數(shù)組中絕不是一個(gè)好想法。例如var arr = [1, “1”, undefined, true, “true”]

  類型推斷的性能測(cè)試

  正如我們所看到的結(jié)果,整數(shù)的數(shù)組是最快的。

  稀疏數(shù)組與滿數(shù)組

  當(dāng)你使用稀疏數(shù)組時(shí),要注意訪問(wèn)元素將遠(yuǎn)遠(yuǎn)慢于滿數(shù)組。因?yàn)閂8不會(huì)分配一整塊空間給只用到部分空間的數(shù)組。取而代之的是,它被管理在字典中,既節(jié)約了空間,但花費(fèi)訪問(wèn)的時(shí)間。

  稀疏數(shù)組與滿數(shù)組的測(cè)試

  預(yù)分配空間VS動(dòng)態(tài)分配

  不要預(yù)分配大數(shù)組(如大于64K的元素),其最大的大小,而應(yīng)該動(dòng)態(tài)分配。在我們這篇文章的性能測(cè)試之前,請(qǐng)記住這只適用部分JavaScript引擎。

空字面量與預(yù)分配數(shù)組在不同的瀏覽器進(jìn)行測(cè)試

  Nitro (Safari)對(duì)預(yù)分配的數(shù)組更有利。而在其他引擎(V8,SpiderMonkey)中,預(yù)先分配并不是高效的。

  預(yù)分配數(shù)組測(cè)試

// Empty array
var arr = [];
for (var i = 0; i < 1000000; i++) {
    arr[i] = i;
}
 
// Pre-allocated array
var arr = new Array(1000000);
for (var i = 0; i < 1000000; i++) {
    arr[i] = i;
}

  優(yōu)化你的應(yīng)用

  在Web應(yīng)用的世界中,速度就是一切。沒(méi)有用戶希望用一個(gè)要花幾秒鐘計(jì)算某列總數(shù)或花幾分鐘匯總信息的表格應(yīng)用。這是為什么你要在代碼中壓榨每一點(diǎn)性能的重要原因。

圖片來(lái)源: Per Olof Forsberg.

  理解和提高應(yīng)用程序的性能是非常有用的同時(shí),它也是困難的。我們推薦以下的步驟來(lái)解決性能的痛點(diǎn):

  • 測(cè)量:在您的應(yīng)用程序中找到慢的地方(約45%)
  • 理解:找出實(shí)際的問(wèn)題是什么(約45%)
  • 修復(fù)它! (約10%)

  下面推薦的一些工具和技術(shù)可以協(xié)助你。

  基準(zhǔn)化(BENCHMARKING)

  有很多方式來(lái)運(yùn)行JavaScript代碼片段的基準(zhǔn)測(cè)試其性能——一般的假設(shè)是,基準(zhǔn)簡(jiǎn)單地比較兩個(gè)時(shí)間戳。這中模式被jsPerf團(tuán)隊(duì)指出,并在SunSpiderKraken的基準(zhǔn)套件中使用:

var totalTime,
    start = new Date,
    iterations = 1000;
while (iterations--) {
  // Code snippet goes here
}
// totalTime → the number of milliseconds taken
// to execute the code snippet 1000 times
totalTime = new Date - start;

  在這里,要測(cè)試的代碼被放置在一個(gè)循環(huán)中,并運(yùn)行一個(gè)設(shè)定的次數(shù)(例如6次)。在此之后,開(kāi)始日期減去結(jié)束日期,就得出在循環(huán)中執(zhí)行操作所花費(fèi)的時(shí)間。

  然而,這種基準(zhǔn)測(cè)試做的事情過(guò)于簡(jiǎn)單了,特別是如果你想運(yùn)行在多個(gè)瀏覽器和環(huán)境的基準(zhǔn)。垃圾收集器本身對(duì)結(jié)果是有一定影響的。即使你使用window.performance這樣的解決方案,也必須考慮到這些缺陷。

  不管你是否只運(yùn)行基準(zhǔn)部分的代碼,編寫(xiě)一個(gè)測(cè)試套件或編碼基準(zhǔn)庫(kù),JavaScript基準(zhǔn)其實(shí)比你想象的更多。如需更詳細(xì)的指南基準(zhǔn),我強(qiáng)烈建議你閱讀由Mathias Bynens和John-David Dalton提供的Javascript基準(zhǔn)測(cè)試。

  分析(PROFILING)

  Chrome開(kāi)發(fā)者工具為JavaScript分析有很好的支持??梢允褂么斯δ軝z測(cè)哪些函數(shù)占用了大部分時(shí)間,這樣你就可以去優(yōu)化它們。這很重要,即使是代碼很小的改變會(huì)對(duì)整體表現(xiàn)產(chǎn)生重要的影響。

Chrome開(kāi)發(fā)者工具的分析面板

  分析過(guò)程開(kāi)始獲取代碼性能基線,然后以時(shí)間線的形式體現(xiàn)。這將告訴我們代碼需要多長(zhǎng)時(shí)間運(yùn)行?!癙rofiles”選項(xiàng)卡給了我們一個(gè)更好的視角來(lái)了解應(yīng)用程序中發(fā)生了什么。JavaScript CPU分析文件展示了多少CPU時(shí)間被用于我們的代碼,CSS選擇器分析文件展示了多少時(shí)間花費(fèi)在處理選擇器上,堆快照顯示多少內(nèi)存正被用于我們的對(duì)象。

  利用這些工具,我們可以分離、調(diào)整和重新分析來(lái)衡量我們的功能或操作性能優(yōu)化是否真的起到了效果。

“Profile”選項(xiàng)卡展示了代碼性能信息。

  一個(gè)很好的分析介紹,閱讀Zack Grossbart的 JavaScript Profiling With The Chrome Developer Tools

  提示:在理想情況下,若想確保你的分析并未受到已安裝的應(yīng)用程序或擴(kuò)展的任何影響,可以使用--user-data-dir <empty_directory>標(biāo)志來(lái)啟動(dòng)Chrome。在大多數(shù)情況下,這種方法優(yōu)化測(cè)試應(yīng)該是足夠的,但也需要你更多的時(shí)間。這是V8標(biāo)志能有所幫助的。

  避免內(nèi)存泄漏——3快照技術(shù)

  在谷歌內(nèi)部,Chrome開(kāi)發(fā)者工具被Gmail等團(tuán)隊(duì)大量使用,用來(lái)幫助發(fā)現(xiàn)和排除內(nèi)存泄漏。

Chrome開(kāi)發(fā)者工具中的內(nèi)存統(tǒng)計(jì)

  內(nèi)存統(tǒng)計(jì)出我們團(tuán)隊(duì)所關(guān)心的私有內(nèi)存使用、JavaScript堆的大小、DOM節(jié)點(diǎn)數(shù)量、存儲(chǔ)清理、事件監(jiān)聽(tīng)計(jì)數(shù)器和垃圾收集器正要回收的東西。推薦閱讀Loreena Lee的“3快照”技術(shù)。該技術(shù)的要點(diǎn)是,在你的應(yīng)用程序中記錄一些行為,強(qiáng)制垃圾回收,檢查DOM節(jié)點(diǎn)的數(shù)量有沒(méi)有恢復(fù)到預(yù)期的基線,然后分析三個(gè)堆的快照來(lái)確定是否有內(nèi)存泄漏。

  單頁(yè)面應(yīng)用的內(nèi)存管理

  單頁(yè)面應(yīng)用程序(例如AngularJS,Backbone,Ember)的內(nèi)存管理是非常重要的,它們幾乎永遠(yuǎn)不會(huì)刷新頁(yè)面。這意味著內(nèi)存泄漏可能相當(dāng)明顯。移動(dòng)終端上的單頁(yè)面應(yīng)用充滿了陷阱,因?yàn)樵O(shè)備的內(nèi)存有限,并在長(zhǎng)期運(yùn)行Email客戶端或社交網(wǎng)絡(luò)等應(yīng)用程序。能力愈大責(zé)任愈重。

  有很多辦法解決這個(gè)問(wèn)題。在Backbone中,確保使用dispose()來(lái)處理舊視圖和引用(目前在Backbone(Edge)中可用)。這個(gè)函數(shù)是最近加上的,移除添加到視圖“event”對(duì)象中的處理函數(shù),以及通過(guò)傳給view的第三個(gè)參數(shù)(回調(diào)上下文)的model或collection的事件監(jiān)聽(tīng)器。dispose()也會(huì)被視圖的remove()調(diào)用,處理當(dāng)元素被移除時(shí)的主要清理工作。Ember 等其他的庫(kù)當(dāng)檢測(cè)到元素被移除時(shí),會(huì)清理監(jiān)聽(tīng)器以避免內(nèi)存泄漏。

  Derick Bailey的一些明智的建議:

與其了解事件與引用是如何工作的,不如遵循的標(biāo)準(zhǔn)規(guī)則來(lái)管理JavaScript中的內(nèi)存。如果你想加載數(shù)據(jù)到的一個(gè)存滿用戶對(duì)象的Backbone集合中,你要清空這個(gè)集合使它不再占用內(nèi)存,那必須這個(gè)集合的所有引用以及集合內(nèi)對(duì)象的引用。一旦清楚了所用的引用,資源就會(huì)被回收。這就是標(biāo)準(zhǔn)的JavaScript垃圾回收規(guī)則。

  在文章中,Derick涵蓋了許多使用Backbone.js時(shí)的常見(jiàn)內(nèi)存缺陷,以及如何解決這些問(wèn)題。

  Felix Geisend?rfer的在Node中調(diào)試內(nèi)存泄漏的教程也值得一讀,尤其是當(dāng)它形成了更廣泛SPA堆棧的一部分。

  減少回流(REFLOWS)

  當(dāng)瀏覽器重新渲染文檔中的元素時(shí)需要 重新計(jì)算它們的位置和幾何形狀,我們稱之為回流?;亓鲿?huì)阻塞用戶在瀏覽器中的操作,因此理解提升回流時(shí)間是非常有幫助的。

回流時(shí)間圖表

  你應(yīng)該批量地觸發(fā)回流或重繪,但是要節(jié)制地使用這些方法。盡量不處理DOM也很重要。可以使用DocumentFragment,一個(gè)輕量級(jí)的文檔對(duì)象。你可以把它作為一種方法來(lái)提取文檔樹(shù)的一部分,或創(chuàng)建一個(gè)新的文檔“片段”。與其不斷地添加DOM節(jié)點(diǎn),不如使用文檔片段后只執(zhí)行一次DOM插入操作,以避免過(guò)多的回流。

  例如,我們寫(xiě)一個(gè)函數(shù)給一個(gè)元素添加20個(gè)div。如果只是簡(jiǎn)單地每次append一個(gè)div到元素中,這會(huì)觸發(fā)20次回流。

function addDivs(element) {
  var div;
  for (var i = 0; i < 20; i ++) {
    div = document.createElement('div');
    div.innerHTML = 'Heya!';
    element.appendChild(div);
  }
}

  要解決這個(gè)問(wèn)題,可以使用DocumentFragment來(lái)代替,我們可以每次添加一個(gè)新的div到里面。完成后將DocumentFragment添加到DOM中只會(huì)觸發(fā)一次回流。

function addDivs(element) {
  var div;
  // Creates a new empty DocumentFragment.
  var fragment = document.createDocumentFragment();
  for (var i = 0; i < 20; i ++) {
    div = document.createElement('a');
    div.innerHTML = 'Heya!';
    fragment.appendChild(div);
  }
  element.appendChild(fragment);
}

  可以參閱 Make the Web Faster,JavaScript Memory Optimization 和 Finding Memory Leaks。

  JS內(nèi)存泄漏探測(cè)器

  為了幫助發(fā)現(xiàn)JavaScript內(nèi)存泄漏,谷歌的開(kāi)發(fā)人員((Marja H?ltt?和Jochen Eisinger)開(kāi)發(fā)了一種工具,它與Chrome開(kāi)發(fā)人員工具結(jié)合使用,檢索堆的快照并檢測(cè)出是什么對(duì)象導(dǎo)致了內(nèi)存泄漏。

一個(gè)JavaScript內(nèi)存泄漏檢測(cè)工具

  有完整的文章介紹了如何使用這個(gè)工具,建議你自己到內(nèi)存泄漏探測(cè)器項(xiàng)目頁(yè)面看看。

  如果你想知道為什么這樣的工具還沒(méi)集成到我們的開(kāi)發(fā)工具,其原因有二。它最初是在Closure庫(kù)中幫助我們捕捉一些特定的內(nèi)存場(chǎng)景,它更適合作為一個(gè)外部工具。

  V8優(yōu)化調(diào)試和垃圾回收的標(biāo)志位

  Chrome支持直接通過(guò)傳遞一些標(biāo)志給V8,以獲得更詳細(xì)的引擎優(yōu)化輸出結(jié)果。例如,這樣可以追蹤V8的優(yōu)化:

"/Applications/Google Chrome/Google Chrome" --js-flags="--trace-opt --trace-deopt"

  Windows用戶可以這樣運(yùn)行 chrome.exe –js-flags=”–trace-opt –trace-deopt”

  在開(kāi)發(fā)應(yīng)用程序時(shí),下面的V8標(biāo)志都可以使用。

  • trace-opt —— 記錄優(yōu)化函數(shù)的名稱,并顯示跳過(guò)的代碼,因?yàn)閮?yōu)化器不知道如何優(yōu)化。
  • trace-deopt —— 記錄運(yùn)行時(shí)將要“去優(yōu)化”的代碼。
  • trace-gc —— 記錄每次的垃圾回收。

  V8的處理腳本用*(星號(hào))標(biāo)識(shí)優(yōu)化過(guò)的函數(shù),用~(波浪號(hào))表示未優(yōu)化的函數(shù)。

  如果你有興趣了解更多關(guān)于V8的標(biāo)志和V8的內(nèi)部是如何工作的,強(qiáng)烈建議 閱讀Vyacheslav Egorov的excellent post on V8 internals。

  HIGH-RESOLUTION TIME 和 NAVIGATION TIMING API

  高精度時(shí)間(HRT)是一個(gè)提供不受系統(tǒng)時(shí)間和用戶調(diào)整影響的亞毫秒級(jí)高精度時(shí)間接口,可以把它當(dāng)做是比 new Date 和 Date.now()更精準(zhǔn)的度量方法。這對(duì)我們編寫(xiě)基準(zhǔn)測(cè)試幫助很大。

高精度時(shí)間(HRT)提供了當(dāng)前亞毫秒級(jí)的時(shí)間精度

  目前HRT在Chrome(穩(wěn)定版)中是以window.performance.webkitNow()方式使用,但在Chrome Canary中前綴被丟棄了,這使得它可以通過(guò)window.performance.now()方式調(diào)用。Paul Irish在HTML5Rocks上了關(guān)于HRT更多內(nèi)容的文章。

  現(xiàn)在我們知道當(dāng)前的精準(zhǔn)時(shí)間,那有可以準(zhǔn)確測(cè)量頁(yè)面性能的API嗎?好吧,現(xiàn)在有個(gè)Navigation Timing API可以使用,這個(gè)API提供了一種簡(jiǎn)單的方式,來(lái)獲取網(wǎng)頁(yè)在加載呈現(xiàn)給用戶時(shí),精確和詳細(xì)的時(shí)間測(cè)量記錄??梢栽赾onsole中使用window.performance.timing來(lái)獲取時(shí)間信息:

顯示在控制臺(tái)中的時(shí)間信息

  我們可以從上面的數(shù)據(jù)獲取很多有用的信息,例如網(wǎng)絡(luò)延時(shí)為responseEnd – fetchStart,頁(yè)面加載時(shí)間為loadEventEnd – responseEnd,處理導(dǎo)航和頁(yè)面加載的時(shí)間為loadEventEnd – navigationStart。

  正如你所看到的,perfomance.memory的屬性也能顯示JavaScript的內(nèi)存數(shù)據(jù)使用情況,如總的堆大小。

  更多Navigation Timing API的細(xì)節(jié),閱讀 Sam Dutton的 Measuring Page Load Speed With Navigation Timing。

  ABOUT:MEMORY 和 ABOUT:TRACING

  Chrome中的about:tracing提供了瀏覽器的性能視圖,記錄了Chrome的所有線程、tab頁(yè)和進(jìn)程。

About:Tracing提供了瀏覽器的性能視圖

  這個(gè)工具的真正用處是允許你捕獲Chrome的運(yùn)行數(shù)據(jù),這樣你就可以適當(dāng)?shù)卣{(diào)整JavaScript執(zhí)行,或優(yōu)化資源加載。

  Lilli Thompson有一篇寫(xiě)給游戲開(kāi)發(fā)者的使用about:tracing分析WebGL游戲的文章,同時(shí)也適合JavaScript的開(kāi)發(fā)者。

  在Chrome的導(dǎo)航欄里可以輸入about:memory,同樣十分實(shí)用,可以獲得每個(gè)tab頁(yè)的內(nèi)存使用情況,對(duì)定位內(nèi)存泄漏很有幫助。

  總結(jié)

  我們看到,JavaScript的世界中有很多隱藏的陷阱,且并沒(méi)有提升性能的銀彈。只有把一些優(yōu)化方案綜合使用到(現(xiàn)實(shí)世界)測(cè)試環(huán)境,才能獲得最大的性能收益。即便如此,了解引擎是如何解釋和優(yōu)化代碼,可以幫助你調(diào)整應(yīng)用程序。

  測(cè)量,理解,修復(fù)。不斷重復(fù)這個(gè)過(guò)程。

圖片來(lái)源: Sally Hunter

  謹(jǐn)記關(guān)注優(yōu)化,但為了便利可以舍棄一些很小的優(yōu)化。例如,有些開(kāi)發(fā)者選擇.forEach和Object.keys代替for和for..in循環(huán),盡管這會(huì)更慢但使用更方便。要保證清醒的頭腦,知道什么優(yōu)化是需要的,什么優(yōu)化是不需要的。

  同時(shí)注意,雖然JavaScript引擎越來(lái)越快,但下一個(gè)真正的瓶頸是DOM?;亓骱椭乩L的減少也是重要的,所以必要時(shí)再去動(dòng)DOM。還有就是要關(guān)注網(wǎng)絡(luò),HTTP請(qǐng)求是珍貴的,特別是移動(dòng)終端上,因此要使用HTTP的緩存去減少資源的加載。

  記住這幾點(diǎn)可以保證你獲取了本文的大部分信息,希望對(duì)你有所幫助!

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買(mǎi)等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多