編寫高效JavaScript代碼

本文是閱讀Writing Fast, Memory-Efficient JavaScript后的總結(jié)和筆記皮服,不是嚴(yán)格意義上的翻譯拱撵,如果有時(shí)間拧略,推薦閱讀原文篓冲。

原則

不要做任何優(yōu)化除非的確需要優(yōu)化

任何的性能優(yōu)化都必須以測(cè)量數(shù)據(jù)為基礎(chǔ)李破,如果你懷疑代碼存在性能問題宠哄,首先通過測(cè)試來驗(yàn)證你的想法。

性能優(yōu)化三問

  1. 我還能做哪些工作從而讓代碼變得更有效率嗤攻?
  2. 流行的JavaScript引擎通常會(huì)做哪些優(yōu)化工作毛嫉?
  3. 哪些優(yōu)化是JavaScript引擎不能做的,垃圾回收器是否能清理我們期望清理的妇菱?

對(duì)JavaScript引擎的深入了解有助于我們編寫高效的JavaScript代碼承粤,但不要只針對(duì)某一特定引擎做性能優(yōu)化。

V8的幾個(gè)關(guān)鍵概念

  • 基礎(chǔ)編譯器闯团,解析你的JavaScript代碼并生成Native Machine Code執(zhí)行辛臊,而不是執(zhí)行字節(jié)碼或是直接對(duì)JavaScript解釋執(zhí)行。
  • 在V8中房交,對(duì)象以object model的形式存在彻舰。對(duì)象在JavaScript中是以關(guān)聯(lián)數(shù)組的形式存在,但V8采用的是Hidden Classes——一種對(duì)查找操作進(jìn)行了優(yōu)化的內(nèi)部類型系統(tǒng)涌萤。
  • 運(yùn)行時(shí)探查器監(jiān)視運(yùn)行中的系統(tǒng)淹遵,并識(shí)別出Hot functions,即是耗用了較長(zhǎng)時(shí)間的代碼
  • 優(yōu)化編譯器重新編譯并優(yōu)化由運(yùn)行時(shí)探查器識(shí)別出來的Hot代碼
  • V8支持反優(yōu)化负溪,優(yōu)化編譯器能夠發(fā)現(xiàn)過度優(yōu)化的代碼并對(duì)其進(jìn)行處理
  • V8有自己的垃圾回收器

垃圾回收

垃圾回收是一種內(nèi)存管理機(jī)制透揣,垃圾回收器會(huì)嘗試清理掉不再被使用的對(duì)象,并回收內(nèi)存川抡。

  • 在絕大多數(shù)情況下都不需要手動(dòng)解除引用
  • 你不可能強(qiáng)制垃圾回收器工作

刪除引用的誤區(qū)

盡可能不要使用delete辐真,在下面的列子中,delete 帶來的弊遠(yuǎn)遠(yuǎn)大于利

var o = { x: 1};
delete o.x;

主要的原因是為了避免在運(yùn)行時(shí)修改Hot對(duì)象的結(jié)構(gòu)崖堤,因?yàn)楣潭ǖ膶?duì)象結(jié)構(gòu)有助于JavaScript引擎對(duì)其進(jìn)行優(yōu)化侍咱,而delete會(huì)導(dǎo)致對(duì)象結(jié)構(gòu)改變

另外一個(gè)誤區(qū)是將對(duì)象設(shè)置為null,將對(duì)象設(shè)置為null不會(huì)刪除對(duì)象密幔,只是將對(duì)象指向null楔脯,這要好過采用delete,但通常也是不必要的胯甩。

全局變量在整個(gè)頁面生命周期中都是不會(huì)被清理的昧廷,無論頁面打開多長(zhǎng)時(shí)間,除非是刷新頁面或者轉(zhuǎn)到其他頁面偎箫。局部變量(Function-scoped)在方法執(zhí)行完后木柬,且沒有被引用的情況下將會(huì)被回收。

所以淹办,請(qǐng)盡量避免使用全局變量

經(jīng)驗(yàn)法則

為了使垃圾回收器盡早回收對(duì)象眉枕,不要保持不必要的對(duì)象引用

  • 比手動(dòng)解除引用更好的方法是將對(duì)象放在合適的變量域中,能用局部變量就不要采用全局變量
  • 當(dāng)事件監(jiān)聽不再需要時(shí),請(qǐng)解除事件綁定速挑,尤其是當(dāng)事件綁定的DOM對(duì)象被刪除時(shí)
  • 如果有使用本地緩存谤牡,請(qǐng)確保有合適的清理機(jī)制(比如時(shí)效機(jī)制),從而避免大量無用的數(shù)據(jù)存儲(chǔ)梗摇。

方法 (Function)

如前面所說拓哟,垃圾回收器只有在對(duì)象不可觸及的時(shí)候才會(huì)對(duì)其做回收處理×媸冢考慮如下兩個(gè)列子

function foo(){
  var bar = new LargeObject()
  bar.someCall();
}
function foo(){
  var bar = new LargeObject()
  bar.someCall();
  return bar;
}
var b = foo();

在第一個(gè)例子中断序,bar指向的對(duì)象會(huì)在方法執(zhí)行完畢后處于可回收狀態(tài);在第二個(gè)列子中糜烹,由于在局部變量外維護(hù)了一個(gè)全局變量b违诗,bar指向的對(duì)象無法被回收。

閉包 (Closures)

當(dāng)一個(gè)方法返回一個(gè)內(nèi)部方法時(shí)疮蹦,被返回的內(nèi)部方法能訪問外部方法的局部變量域即使外部方法已經(jīng)執(zhí)行完畢诸迟。

function sum(x){
  function sumIt(y){
    return x + y;
  }
}
var sumA = sum(4);
var sumB = sumA(3);

在上面的例子中,sumIt方法即使處于sum的局部變量域中愕乎,但由于存在一個(gè)sumA全局變量阵苇,在sum執(zhí)行完畢后也無法被回收。

再看兩個(gè)例子

var a = function(){
  var largeObj = new LargeObject();
  return function(){
    return largeObj;
  }
}();
var a = function(){
  var smallObj = new SmallObj();
  var largeObj = new LargeObj();
  return function(n){
    return smallObj;
  }
}();

第一個(gè)例子中感论,largeObj可以通過變量a訪問绅项,因此不可被回收;在第二個(gè)例子中比肄,方法一旦執(zhí)行完畢快耿,largeObj就無法被訪問了,因此處于可回收狀態(tài)芳绩。

定時(shí)器 (Timer)

setTimeout / setInterval 方法中的引用掀亥,只有當(dāng)定時(shí)器執(zhí)行完成后才能被回收

V8優(yōu)化小貼士

  • 某些行為會(huì)導(dǎo)致V8停止優(yōu)化工作,比如try-catch, 為了能弄清哪些代碼可以被優(yōu)化妥色,哪些不能搪花,你可以在V8命令行工具中使用—trace-opt file.js獲得有用的信息。

  • 如果你在意速度嘹害,那就盡可能保證你的方法是”單形的(monomophic)"

    不要做類似如下的嘗試

    function add(x, y){
      return x+y;
    }
    add(1,2);
    add('a','b');
    add(my_custom_object, undefined);
    
  • 不要加載沒有被初始化或者已被刪除的元素鳍侣,盡管在輸出上沒有不同,但卻會(huì)讓代碼變得更慢

  • 不要寫大方法吼拥,因?yàn)樗麄兒茈y被優(yōu)化。

對(duì)象還是數(shù)組线衫, 如何選擇凿可?

  • 如果存儲(chǔ)的是大量數(shù)字,或者是相同類型的對(duì)象列表,采用數(shù)組枯跑;

  • 如果根據(jù)語義你需要一個(gè)有很多屬性的對(duì)象惨驶,那就采用對(duì)象,在內(nèi)存利用方面這會(huì)很高效敛助,同時(shí)也很快粗卜;

  • 無論是數(shù)組還是對(duì)象,采用整數(shù)索引都最快的纳击。

    var sum = 0;
    for (var x=0; x<arr.length; ++x){
      sum + = arr[x].payload;
    }
    
    var sum = 0;
    for(var x in obj){
      sum += obj[x].payload;
    }
    
    var sum = 0;
    for(var x=0; x<1000,++x){
      sum += obj[x].payload;
    }
    
    var sum = 0;
    var keys = Objects.keys(obj);
    for(var x=0; x<keys.length;++x){
      sum += obj[keys[x]].payload;
    }
    

    在上面的四段代碼中续扔,第一段和第三段速度比第二段和第四段要快很多。其中焕数,第一段代碼執(zhí)行最快纱昧,最后一段代碼執(zhí)行最慢。

  • 相比數(shù)組中的元素堡赔,對(duì)象的屬性在結(jié)構(gòu)上相對(duì)復(fù)雜识脆。在引擎層面,內(nèi)存中越是簡(jiǎn)單的結(jié)構(gòu)越容易被優(yōu)化善已,尤其是包含數(shù)字的數(shù)組灼捂。因此,如果你需要向量换团,采用數(shù)組而不是一個(gè)包含x, y, z屬性的對(duì)象會(huì)有更優(yōu)的性能表現(xiàn)悉稠。

在JavaScript中,數(shù)組和對(duì)象最重要的不同是數(shù)組的length屬性啥寇,如果你能自己維護(hù)這個(gè)值偎球,對(duì)象在V8中也能跑出數(shù)組的速度。

使用對(duì)象的性能小貼士

  • 使用構(gòu)造函數(shù)創(chuàng)建對(duì)象辑甜,因?yàn)樗胁捎猛粯?gòu)造函數(shù)創(chuàng)建的對(duì)象都具有相同的hidden class衰絮,另外,采用構(gòu)造函數(shù)創(chuàng)建對(duì)象也比Object.create()這種方法略塊磷醋。
  • 盡管JavaScript沒有限制類型數(shù)量和對(duì)象的復(fù)雜度猫牡,但長(zhǎng)原型鏈和大量的對(duì)象屬性會(huì)對(duì)性能造成損害。因此盡可能保持較短的原型鏈和較少的對(duì)象屬性邓线。

對(duì)象的拷貝

for..in循環(huán)是性能殺手淌友,通過該方法遍歷對(duì)象屬性進(jìn)行拷貝非常低效『С拢拷貝大對(duì)象始終會(huì)降低性能震庭,盡可能不要干這樣的事情,當(dāng)然大對(duì)象的存在本身就是一個(gè)錯(cuò)誤你雌。如果你確實(shí)需要在性能攸關(guān)的代碼中拷貝對(duì)象器联,可以采用如下的方式二汛。

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

緩存采用模塊化編程(Module Pattern)的方法

// prototypal

Klass1 = function(){}
Klass1.prototype.foo = function(){
  log('foo');
}
Klass1.prototype.bar = function(){
  log('bar');
}
// Module pattern
Klass2 = function(){
  var foo = function(){
    log('foo');
  }
  var bar = function(){
    log('bar');
  }
  return {foo:foo,bar:bar}
}
// Module pattern with cached functions
var fooFn = function(){
  log('foo');
}
var barFn = function(){
  log('bar')
}
Klass3 = function(){
  return{
    foo: fooFn,
    bar: barFn
  }
}

執(zhí)行速度從快到慢依次是

Module Pattern with Cached functions → prototypal → Module pattern

使用數(shù)組的性能小貼士

  • 不要?jiǎng)h除數(shù)組元素,當(dāng)數(shù)組的Key set分布分散后拨拓,V8會(huì)將存儲(chǔ)方式轉(zhuǎn)為字典肴颊,導(dǎo)致速度變慢。
  • 數(shù)組常量更高效渣磷,尤其是小數(shù)組和中等大小的數(shù)組婿着。
var a = [1, 2, 3, 4]
var a = [];
for(var i=1, i<=4; i++){
  a.push(i);
}

不要采用第二段代碼中的方法初始化數(shù)組。

  • 不要在數(shù)組中存儲(chǔ)不同類型的元素
  • V8中醋界,稀疏數(shù)組( Sparse Arrays)是被當(dāng)成字典對(duì)待的竟宋,因此相比密集數(shù)組(Full Arrays),執(zhí)行速度更慢
  • 與緊湊的數(shù)組相比物独,滿身是洞的數(shù)組執(zhí)行更慢袜硫,即使是從密集數(shù)組中刪除一個(gè)元素,也會(huì)帶來性能上的損失挡篓。
  • 不要預(yù)先給大數(shù)組(大于64k)分配一個(gè)最大值
var arr = [];
for(var i = 0; i< 1000000; i++){
  arr[i] = 1;
}
var arr = new Array(1000000);
for(var i=0; i<1000000; i++){
  arr[i]=i;
}

需要注意的是婉陷,不同引擎在這一點(diǎn)上有不同,在Nitro(Safari)中官研,第二段代碼跑得更快秽澳,但在V8(Chrome), SpiderMonkey(Firefox)中,第一段更快戏羽。

公眾號(hào)搜索:front_end_talk
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末担神,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子始花,更是在濱河造成了極大的恐慌妄讯,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,542評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件酷宵,死亡現(xiàn)場(chǎng)離奇詭異亥贸,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)浇垦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門炕置,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人男韧,你說我怎么就攤上這事朴摊。” “怎么了此虑?”我有些...
    開封第一講書人閱讀 163,912評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵甚纲,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我朦前,道長(zhǎng)贩疙,這世上最難降的妖魔是什么讹弯? 我笑而不...
    開封第一講書人閱讀 58,449評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮这溅,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘棒仍。我一直安慰自己悲靴,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評(píng)論 6 392
  • 文/花漫 我一把揭開白布莫其。 她就那樣靜靜地躺著癞尚,像睡著了一般。 火紅的嫁衣襯著肌膚如雪乱陡。 梳的紋絲不亂的頭發(fā)上浇揩,一...
    開封第一講書人閱讀 51,370評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音憨颠,去河邊找鬼胳徽。 笑死,一個(gè)胖子當(dāng)著我的面吹牛爽彤,可吹牛的內(nèi)容都是我干的养盗。 我是一名探鬼主播,決...
    沈念sama閱讀 40,193評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼适篙,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼往核!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起嚷节,我...
    開封第一講書人閱讀 39,074評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤聂儒,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后硫痰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體衩婚,經(jīng)...
    沈念sama閱讀 45,505評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評(píng)論 3 335
  • 正文 我和宋清朗相戀三年碍论,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了谅猾。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,841評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡鳍悠,死狀恐怖税娜,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情藏研,我是刑警寧澤敬矩,帶...
    沈念sama閱讀 35,569評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站蠢挡,受9級(jí)特大地震影響弧岳,放射性物質(zhì)發(fā)生泄漏凳忙。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評(píng)論 3 328
  • 文/蒙蒙 一禽炬、第九天 我趴在偏房一處隱蔽的房頂上張望涧卵。 院中可真熱鬧,春花似錦腹尖、人聲如沸柳恐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽乐设。三九已至,卻和暖如春绎巨,著一層夾襖步出監(jiān)牢的瞬間近尚,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評(píng)論 1 269
  • 我被黑心中介騙來泰國打工场勤, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留戈锻,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,962評(píng)論 2 370
  • 正文 我出身青樓却嗡,卻偏偏與公主長(zhǎng)得像舶沛,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子窗价,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • JavaScript絕對(duì)是最火的編程語言之一如庭,一直具有很大的用戶群,隨著在服務(wù)端的使用(NodeJs)撼港,更是爆發(fā)了...
    不去解釋閱讀 2,415評(píng)論 1 16
  • 原文: https://github.com/ecomfe/spec/blob/master/javascript...
    zock閱讀 3,371評(píng)論 2 36
  • 今天是2017.8.27r坪它。 早上收到預(yù)警sz今天又有臺(tái)風(fēng),打開手機(jī)看了倒計(jì)時(shí)帝牡,只剩下97天了往毡,可怕。自己還沒怎么...
    是藍(lán)先生閱讀 515評(píng)論 0 0
  • 辭職探親靶溜,數(shù)日仍沒有見到父親开瞭。 在豫西的小縣城,只有連綿的丘陵罩息,山在豫西的西方嗤详,偶爾可以遙望,朦朧的輪廓瓷炮。父親...
    野西米閱讀 433評(píng)論 0 0
  • (還鄉(xiāng)團(tuán)歸來.宴籌)明月幾時(shí)照東方葱色,萬里赤子要?dú)w來。它方有情這里暖娘香,那邊同學(xué)還鄉(xiāng)團(tuán)苍狰。心有緣由自開放办龄,身在異國思故鄉(xiāng)...
    甘朝武閱讀 484評(píng)論 0 0