GC 算法
1. 引用計數
核心思想:設置引用數,判斷當前引用是否為0
優點:
- 發現垃圾時,立即回收
- 最大限度減少程序暫停
缺點:
- 無法回收循環引用的對象
- 時間開銷大(需要監聽計數值的變化)
function fn() {
const obj1 = {}
const obj2 = {}
}
2. 標記清除
核心思想:分標記和清除兩個階段
缺點:
1、 空間碎片化(回收對象在地址上的不連續)
2、 不能立即回收
3. 標記整理
標記,然後將活動對象的地址進行整理,儘量使得活動對象地址連續
4. 分代回收
回收新生代對象(存活時間較短的變量對象)
- 回收過程採用複製算法+標記整理
- 新生代內存區分為兩個等大小的空間
- 使用空間為From,用於存儲活動對象, 空閒空間為To,用於存儲From中的活動對象
- 活動對象存儲於From空間
- 標記整理後將活動對象拷貝至To
- From 與To交換空間完成釋放
回收細節:From和To之間的拷貝過程可能出現晉升(將新生代對象移動至老生代)下面兩種情況會產生晉升
- 一輪GC後還存活的新生代需要晉升
- To空間的使用率超過了25%
回收老生代對象
空間大,無法使用複製算法,使用增量標記法優化效率
- 標記清除
- 標記整理: 使得活動對象的地址連續
- 標記增量: 將原本標記整理的工作拆分為多個小的標記工作,防止程序阻塞
V8
v8垃圾回收策略
v8內存有上限
- 採用分代回收思想
- 內存分為新生代,老生代
- 針對不同對象,採用不同算法
常用算法
- 分代回收
- 空間複製
- 標記清除
- 標記整理
- 標記增量
頻繁GC(垃圾回收)會帶來什麼
- GC工作時,應用程序是停止的
- 頻繁且過長的GC會導致應用假死
- 用户使用中感知應用卡頓
如何確定是否頻繁回收垃圾:
- Timeline中頻繁的上升下降
- 任務管理器中數據頻繁的增加減小
堆快照查找分離dom,這個都是無用的dom引用,需要使用堆快照功能超找
detachedNode
v8引擎執行流程
代碼優化 函數嵌套會導致v8進行多次的預解析,因此不要嵌套太深
堆棧操作
- js執行環境
- 執行環境棧(ECStack, execution context stack)
- 執行上下文
- VO(G),全局變量對象
- EC(G),全局執行上下文
基本類型 (棧操作)
- 基本數據類型是按值進行操作
- 基本數據類型值是存放在棧區的
- 無論我們當前看到的棧內存,還是後續引用數據類型會使用的堆內存都屬於計算機內存
- GO(全局對象)
引用類型 (堆操作)
-
函數執行
- 確定作用域鏈(當前執行上下文,上級執行上下文)
- 確定this指向,如果是在全局作用域那麼就是window
- 初始化arguments對象
- 形參賦值
- 變量提升(var聲明的關鍵字,或者函數內部的function聲明)
- 執行代碼
- 如果函數內部沒有被其他地方所引用,那麼就會進行出棧操作,釋放棧內存
-
閉包理解
function fn() { var a = 1 return function(b) { console.log(a + b) } } const f = fn() f(5) f10()- 閉包是一種機制,通過私有的上下文來保護其中的變量的一種機制
- 也可以認為,在創建的某一個執行上下文不被釋放的時候 就形成了閉包
- 保護、保存數據
循環添加事件,看閉包使用的取捨
如果使用閉包,進行多個事件的註冊,因為閉包的原因,會申請一個對地址來保存,如果事件越多,那麼申請的內存越多,因此需要用到事件代理
變量的申明
變量申明最好是放在局部變量,否則代碼在執行的時候 查找作用域鏈上的變量會比較花時間, 對比下面兩段代碼 使用的是 jsbench這個工具
var i, str = ""
function parseDom() {
for(i = 0; i < 100; i++) {
str += i
}
}
parseDom()
function parseDom() {
let str = ''
for(let i = 0; i < 100; i++) {
str += i
}
}
parseDom()
變量緩存
- 數組長度
- 多次使用的dom變量等
減少判斷層級
- 儘可能的減少判斷的層級,如果有嵌套判斷,看看是否可以將判斷條件往外提