JavaScript 語言性能優(yōu)化

說明: 具體從內(nèi)存空間的使用以及垃圾回收機(jī)制的角度出發(fā)过蹂。

內(nèi)存管理

  • 為什么要進(jìn)行內(nèi)存管理


    內(nèi)存.png

    內(nèi)存管理是可以避免程序出現(xiàn)一些不可察覺的內(nèi)存問題,比如內(nèi)存泄漏糊闽,當(dāng)這些問題反復(fù)出現(xiàn)在代碼中就會有意想不到的bug

  • 內(nèi)存管理
    • 內(nèi)存:可讀寫的單員組成邢锯,表示一片操作空間
    • 管理:操作一片空間的申請,使用辜妓,釋放

申請空間-使用空間-釋放空間

// 申請空間
let obj = { name: 'glh' };
// 使用空間
let ali = obj;
// 釋放空間
obj = null;

JavaScript中的垃圾

  • JavaScript 中的內(nèi)存管理是自動的
  • 對象不再被引用時(shí)被看作是垃圾
  • 對象不能從根上訪問
可達(dá)對象
  • 從跟上出發(fā)可以訪問到的對象就是可達(dá)對象(引用叁幢,作用域鏈)
  • JavaScript的根可以理解為全局變量

GC 算法

  • GC 就是垃圾回收機(jī)制的簡寫
  • GC可以找到內(nèi)存中的垃圾诉濒,并釋放和回收空間

所謂的GC算法就是垃圾回收器工作的時(shí)候查找和回收所遵循的規(guī)則

常見的GC算法
  • 引用計(jì)數(shù)
  • 標(biāo)記清除
  • 標(biāo)記整理
  • 分代回收

引用計(jì)數(shù)

在內(nèi)部維護(hù)一個引用計(jì)數(shù)器秽澳,給每個對象設(shè)置引用數(shù)贷币,當(dāng)引用關(guān)系發(fā)生變化時(shí)须蜗,判斷當(dāng)前對象引用數(shù)是否為 0积糯,當(dāng)為 0 時(shí)候榜苫,當(dāng)前對象為垃圾對象跌帐,被 GC 回收且釋放內(nèi)存空間

  • 優(yōu)點(diǎn):1首懈,發(fā)現(xiàn)垃圾對象,立即回收谨敛;2究履,最大限度減少程序暫停。

  • 缺點(diǎn):1脸狸,無法回收循環(huán)引用的對象最仑;2,時(shí)間開銷大

function(){
  const obj1 = {}
  const obj2 = {}
  obj1.name =  obj2
  obj2.name = obj1  
   return ''
}
f()

以上代碼在采用引用計(jì)數(shù)算法進(jìn)行垃圾回收時(shí)炊甲,并不會對obj1和obj2進(jìn)行回收泥彤,因?yàn)榛ハ啻嬖谝?/p>

如果一個對象的引用數(shù)為 0,則該對象將被回收蜜葱。程序運(yùn)行時(shí)全景,對內(nèi)存的消耗除邏輯代碼外,也包括了 GC 算法的消耗牵囤,引用計(jì)數(shù)固然可以在程序出現(xiàn)垃圾的時(shí)候可以及時(shí)回收釋放內(nèi)存爸黄,也因內(nèi)存可以不斷得到釋放而減少了程序暫停的時(shí)間,但是 GC 同時(shí)也需要維護(hù)‘roots’表來統(tǒng)計(jì)引用計(jì)數(shù)揭鳞,當(dāng)代碼中引用較多時(shí)炕贵,也會帶來損耗。同時(shí)對于 對象之間互有引用的情況野崇,即使對象本身沒有被使用称开,但是引用存在就導(dǎo)致了引用計(jì)數(shù)不為 0,無法被回收的情況乓梨。

標(biāo)記清除算法

  • 將標(biāo)記和清除分為兩個階段
  • 遍歷所有對象找到所有活動對象標(biāo)記
  • 遍歷所有對象清除沒有標(biāo)記的對象
  • 回收相應(yīng)的空間

標(biāo)記清除算法會遞歸的尋找對象之間的引用獲取所有可達(dá)對象鳖轰,并為其做上標(biāo)記, 但是標(biāo)記清除算法所回收的內(nèi)存地址在內(nèi)存上不一定是連續(xù)的,這就導(dǎo)致了內(nèi)存空間的碎片化(類似磁盤碎片), 浪費(fèi)空間扶镀,并且標(biāo)記清除法是不會立即回收對象的蕴侣,而且當(dāng)標(biāo)記清除算法運(yùn)行的時(shí)候,程序會被暫停

優(yōu)點(diǎn):循環(huán)引用的對象也會被回收 缺點(diǎn):不會立即回收垃圾對象

標(biāo)記整理算法

標(biāo)記整理算法增強(qiáng)了標(biāo)記清除算法臭觉,遍歷所有內(nèi)存對象昆雀,將所有可達(dá)內(nèi)存對象標(biāo)記辱志,其在回收非活動對象前會將對象的地址進(jìn)行移動,使其在地址上連續(xù)狞膘,然后再回收揩懒。

優(yōu)點(diǎn):不會存在內(nèi)存空間的碎片化 缺點(diǎn):不會立即回收垃圾對象

V8 簡介

  • V8引擎是一款主流的JavaScript執(zhí)行引擎
  • 采用即使編輯,代碼可以直接轉(zhuǎn)成機(jī)器碼運(yùn)行
  • V8 的運(yùn)行內(nèi)存上限是 1.5G(32 位系統(tǒng)為 800M)

由于 V8 的運(yùn)行內(nèi)存是有上限的挽封,因此垃圾回收需要使用分代回收算法已球,然后針對于新/老生代對象采取不同的 GC 算法

V8中常用的GC算法
  • 分代回收
  • 空間復(fù)制
  • 標(biāo)記清除
  • 標(biāo)記整理
  • 標(biāo)記增量
V8的垃圾回收策略
  • V8 內(nèi)存一分為二(新生代,老生代)
  • 針對不同代對象采用不同GC算法


    回收策略.png
新生代對象的回收操作
  • 小空間用于存儲新聲代對象 32M(32 位為 16M)

  • 新生代對象指存活時(shí)間較短對象(局部作用域的對象辅愿,經(jīng)過一輪 GC 就會被回收的對象)

  • 新生代對象采用采用復(fù)制算法 + 標(biāo)記整理進(jìn)行回收

  • 新生代內(nèi)存區(qū)同樣會被一分為二(等大小 From & To)

  • 活動對象存儲在From空間內(nèi)和悦,當(dāng) From 空間應(yīng)用到一定大小的時(shí)候就會觸發(fā)GC操作使用標(biāo)記整理并整理活動對象的地址,使其連續(xù)然后將活動對象拷貝至 To渠缕,然后 From 空間進(jìn)行內(nèi)存釋放鸽素。

  • 當(dāng)完成一次 GC 操作之后,F(xiàn)rom 和 To 需要進(jìn)行置換亦鳞。

細(xì)節(jié)說明:

  • 在從FromTo拷貝的過程中有可能出現(xiàn)變量晉升的情況馍忽,變量晉升就是新生代的對象移動到老生代。

    晉升條件:

    • 一輪 GC 執(zhí)行完畢之后還存活的新生代則需要晉升燕差。

    • 當(dāng) To 空間的使用率超過 25%的時(shí)候遭笋,同樣需要將此次的活動對象均移動到老生代中。

老生代對象的回收操作
  • 老生代區(qū)域大小約 1.4G(32 位大小為 700M)徒探,老生代存放的對象為存活時(shí)間較長的對象瓦呼,一般為 window 下的變量或被閉包保存的變量。

  • 老生代區(qū)域采用標(biāo)記清除测暗,標(biāo)記整理央串,增量標(biāo)記的 GC 算法。首先使用標(biāo)記清除完成對垃圾空間的回收碗啄,當(dāng)新生代區(qū)域出現(xiàn)晉升現(xiàn)象時(shí)质和,如果老生代空間不足,則會使用標(biāo)記整理進(jìn)行空間優(yōu)化稚字。同時(shí)在老生代變量進(jìn)行標(biāo)記的時(shí)候也會采用增量標(biāo)記算法進(jìn)行效率優(yōu)化饲宿。

  • 增量標(biāo)記是對標(biāo)記清除算法的優(yōu)化,讓其不會一口氣的去尋找到所有活動對象胆描。而是會穿插在程序的運(yùn)行中執(zhí)行瘫想,降低了程序的卡頓,當(dāng)標(biāo)記徹底采集完畢之后昌讲,才會把程序停下來国夜,進(jìn)行垃圾回收。

新老生代垃圾回收細(xì)節(jié)對比
  • 新生代區(qū)域剧蚣,采用復(fù)制算法支竹, 因此其每時(shí)每刻內(nèi)部都有空閑空間的存在(為了完成 From 到 To 的對象復(fù)制),但是新生代區(qū)域空間較小(32M)且被一分為二鸠按,所以這種空間上的浪費(fèi)也是比較微不足道的礼搁。

  • 老生代因其空間較大(1.4G),如果同樣采用一分為二的做法則對空間大小是比較浪費(fèi),且老生代空間較大目尖,存放對對象也較多馒吴,如果進(jìn)行復(fù)制算法,則其消耗對時(shí)間也會更大瑟曲。也就是是否使用復(fù)制算法來進(jìn)行垃圾回收饮戳,是一個時(shí)間 T 關(guān)于內(nèi)存大小的關(guān)系,當(dāng)內(nèi)存較小時(shí)洞拨,使用復(fù)制算法消耗的時(shí)間是比較短的扯罐,而當(dāng)內(nèi)存較大時(shí),采用復(fù)制算法對時(shí)間對消耗也就更大烦衣。

內(nèi)存問題的外在表現(xiàn)
  • 頁面出現(xiàn)延遲加載或經(jīng)常性暫停: 可能存在頻繁當(dāng)GC操作,存在一些代碼瞬間吃滿了內(nèi)存歹河。
  • 頁面出現(xiàn)持續(xù)性的糟糕性能: 程序?yàn)榱诉_(dá)到最優(yōu)的運(yùn)行速度,向內(nèi)存申請了一片較大的內(nèi)存空間花吟,但空間大小超過了設(shè)備所能提供的大小秸歧。
  • 頁面使用隨著時(shí)間延長越來越卡: 可能存在內(nèi)存泄漏。
使用任務(wù)管理器查看內(nèi)存

喚起瀏覽器自帶的任務(wù)管理器衅澈,觀察 js 內(nèi)存键菱,如果 js 內(nèi)存在持續(xù)增大,則存在內(nèi)存問題今布。

Timeline 記錄內(nèi)存

打開 Timeline 開始錄制经备,進(jìn)行頁面操作,結(jié)束錄制之后部默,開啟內(nèi)存勾選弄喘,拖動截圖到指定時(shí)間段查看發(fā)生內(nèi)存問題時(shí)候到頁面展示,并定位問題甩牺。同時(shí)可以查看對應(yīng)出現(xiàn)紅點(diǎn)到執(zhí)行腳本蘑志,定位問題代碼。

利用瀏覽器控制臺內(nèi)存模塊贬派,查找分離 dom

在頁面上進(jìn)行相關(guān)操作后急但,進(jìn)行“拍照”,在快照中查找Detached HTMLElement,回到代碼中查找對應(yīng)的分離 dom 存在的代碼搞乏,在相關(guān)操作代碼之后波桩,對分離 dom 進(jìn)行釋放,防止內(nèi)存泄漏请敦。

如何確定頻繁的垃圾回收操作
  • GC 工作時(shí)镐躲,程序是暫停的储玫,頻繁/過長的 GC 會導(dǎo)致程序假死,用戶會感知到卡頓萤皂。
  • 查看 Timeline 中是否存在內(nèi)存走向在短時(shí)間內(nèi)頻繁上升下降的區(qū)域撒穷。瀏覽器任務(wù)管理器是否頻繁的增加減少。

代碼優(yōu)化

慎用全局變量
  • 全局變量定義在全局執(zhí)行的上下文,是所有作用域鏈的頂端

  • 全局執(zhí)行上下文一直存在于上下文執(zhí)行棧裆熙,直到程序退出

  • 如果某個局部作用域出現(xiàn)了同名變量則會屏蔽或者污染全局作用域

  • 全局變量的執(zhí)行速度端礼,訪問速度要低于局部變量,因此對于一些需要經(jīng)常訪問的全局變量可以在局部作用域中進(jìn)行緩存

一些性能上的小問題
  • 通過原型對象添加方法與直接在對象上添加成員方法相比入录,原型對象上的屬性訪問速度較快蛤奥。

  • 當(dāng)回調(diào)函數(shù)可以單獨(dú)抽離的時(shí)候,其執(zhí)行速度要較快僚稿。

  • 直接訪問屬性凡桥,會比通過方法訪問屬性速度來的快。

  • loop 遍歷速度 forEach > 優(yōu)化 for > for in

  • 節(jié)點(diǎn)克隆(cloneNode)生成節(jié)點(diǎn)速度要快于創(chuàng)建節(jié)點(diǎn)蚀同。

  • 字面量聲明的數(shù)據(jù)生成速度要快于單獨(dú)屬性賦值行為生成的數(shù)據(jù)唬血。
    ......

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市唤崭,隨后出現(xiàn)的幾起案子拷恨,更是在濱河造成了極大的恐慌,老刑警劉巖谢肾,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件腕侄,死亡現(xiàn)場離奇詭異,居然都是意外死亡芦疏,警方通過查閱死者的電腦和手機(jī)冕杠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來酸茴,“玉大人分预,你說我怎么就攤上這事⌒胶矗” “怎么了笼痹?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長酪穿。 經(jīng)常有香客問我凳干,道長,這世上最難降的妖魔是什么被济? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任救赐,我火速辦了婚禮,結(jié)果婚禮上只磷,老公的妹妹穿的比我還像新娘经磅。我一直安慰自己泌绣,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布预厌。 她就那樣靜靜地躺著阿迈,像睡著了一般。 火紅的嫁衣襯著肌膚如雪配乓。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天惠毁,我揣著相機(jī)與錄音犹芹,去河邊找鬼。 笑死鞠绰,一個胖子當(dāng)著我的面吹牛腰埂,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蜈膨,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼屿笼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了翁巍?” 一聲冷哼從身側(cè)響起驴一,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎灶壶,沒想到半個月后肝断,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡驰凛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年胸懈,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片恰响。...
    茶點(diǎn)故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡趣钱,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出胚宦,到底是詐尸還是另有隱情首有,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布枢劝,位于F島的核電站绞灼,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏呈野。R本人自食惡果不足惜低矮,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望被冒。 院中可真熱鬧军掂,春花似錦轮蜕、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至终议,卻和暖如春汇竭,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背穴张。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工细燎, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人皂甘。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓玻驻,卻偏偏與公主長得像,于是被迫代替她去往敵國和親偿枕。 傳聞我的和親對象是個殘疾皇子璧瞬,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評論 2 355