javascript性能优化
本文最后更新于:2022年7月12日 下午
内存管理
js 的内存管理是自动的
let obj1 = {}; // 申请空间
obj1.name = "mmm"; // 使用空间
obj1 = null; // 释放
内存问题
- 内存泄漏
- 内存膨胀
- 频繁垃圾回收
分离 DOM
- 页面上不装载的 DOM
- 垃圾对象引用的 DOM
- 活动对象引用的 DOM
垃圾回收
不是可达对象就被视为内存垃圾
可达对象
- 可以访问到的对象
- 从根(全局)出发访问
GC 算法
引用计数
引用计数器判断引用计数
- 发现垃圾时立即回收(对象上保存了计数器)
- 最大限度减少程序暂停
- 无法回收循环引用
- 资源开销较大(计数记录)
function test() {
const o1 = {};
const o2 = {};
// 从根无法访问到o1,o2
// 但是o1,o2的引用计数都不是0
// 无法被回收
o1.a = o2;
o2.a = o1;
return 123;
}
test();
标记清除
两次遍历所有对象,1 标记可达对象,2 清除没有标记的对象
- 空间碎片化:回收的空间可能很碎( 内存地址不连续)不能满足新变量的需求
- 可以回收循环引用
- 不能立即回收
标记整理
标记清除并且会把对象存储的内存地址进行整理移动
分代回收
性能工具
- performance
- timeline
- memory
- Head Snapshot => 分离 DOM
v8
- js 执行引擎
- 优秀的垃圾回收
- 即时编译
- 内存设限
v8 使用的 GC 算法
分代回收
内存分为新生代,老生代对象
新生代 | 老生代 | |
64位:32M / 32位:16M | 64位:1.4G / 32位:700M | |
From | To | 老生代存储区 |
新生代空间
存活时间较短的对象
- 平分两个等大空间
- 使用空间为
From
,空闲空间为To
- 活动对象存储在
From
-
标记整理
后将活动对象拷贝到To
-
From
完全释放与To
交换
晋升
将新生代对象移动到老生代
- 一轮 GC 后还存活的对象进行晋升
To
空间使用率超过 25%
老生代空间
存活时间较长的对象(全局变量,闭包内的变量)
- 主要使用
标记清除
-
晋升
发生时,如果出现空间不足则会标记整理
-
增量标记
提高效率(将垃圾回收分步执行,与程序执行交替进行,减少程序单次等待时间)
堆栈处理
堆栈准备
- 执行环境栈
ECStack - execution context stack
- 执行上下文
EC(G)
- 全局上下文
VO(G)
- 全局对象
GO
执行环境栈ECStack | 全局对象GO | ||||
执行上下文EC(G) | 私有执行上下文EC(func) | ...... | setInterval setTimeout | ||
全局上下文VO(G) | 其他代码 | 局部上下文AO | 其他代码 | ||
可通过window 访问GO 存放全局变量 | {..func().} | ... | ... |
0x000
0x001
0x002
0x003 【func】
[[scope]]:EC(G)
确定作用域链
...
...
var obj1 = {x:100}
var obj2 = obj1
obj1.y = obj1 = {x:200}
// 先执行 obj1.y = {x:200}
// 后执行 obj1 = {x:200}
// {x:200}里没有.y
obj1.y => undefined
obj2 => {x:100,y:{x:200}}
var arr = ['xxx','aaa']
function foo(obj){
obj[0] = 'yyy' // obj = arr = ['yyy','aaa']
obj = ['nb']
obj[1] = 'mmm' // obj['nb','mmm']
console.log(obj)
}
foo(arr)
变量查找:顺着作用域链依次访问外部上下文 <AO1,AO2,…VO>
[[scope]] 函数书写时确定,所在上下文
var a = 1
function foo(){
var b = 2
return function(c){
console.log(c+b++)
}
}
var f = foo()
f(5) // 7 5+2
f(10) // 13 10+3
let a = 10
function foo(a){
return function(b){
console.log(b+(++a))
}
}
let fn = foo(10)
fn(5) // 16
foo(6)(7) // 14
fn(20) // 32
优化手段
变量局部化
缩短访问数据时查找路径,变量存储位置离使用的地方越近越好
// bad
var i,str = ''
function packageDom(){
for(i = 0 ; i < 1000 ;i++>){
str += i // 每次循环都要去VO(G)中才能找到str,i
}
return str
}
// good
function packageDom(){
let str = ''
for(var i = 0 ; i < 1000 ;i++>){
str += i // 不用访问EC(G)
}
return str
}
减少访问层级
// bad
function Person(name){
this.name = 'personnnn'
this.getName = () => {
return this.name
}
}
const p1 = new Person()
p1.getName()
// good
function Person(name){
this.name = 'personnnn'
}
const p1 = new Person()
p1.name
缓存数据
缓存需要多次使用的数据
// bad
for(let i = 0 ;i < 100000 ;i++){
handle(xxxObj.key1)
}
// good
const cacheItem = xxxObj.key1
for(let i = 0 ;i < 100000 ;i++){
handle(cacheItem)
}
减少判断,提前return
// bad
const arr = [1,2,3,4,5]
function do(k1,k2){
if(k1){
if(arr.includes(k1)){
return 'includes'
if(k2>5){
return 666
}
}
}else{
return 'no argument'
}
}
// good
const arr = [1,2,3,4,5]
function do(k1,k2){
if(!k1) return 'no argument'
if(!arr.includes(k1)) return
if(k2>5){
return 666
}
}
减少循环体内操作
// bad
for(let i = 0 ; i< arr.length ; i++){
...
}
// good
const len = arr.length
for(let i = 0 ; i < len ; i++){
...
}
// better
while(len--){
...
}
字面量和构造函数
// bad
const o1 = new Object()
o1.a = 666
const str = new String('mmm') // 特别是基本类型,性能损耗很大
// good
const o1 = {
a : 666
}
const str = 'mmm' // 字面量调用方法时会被包装为对象
防抖
节流
javascript性能优化
http://yoursite.com/2022/03/31/js性能优化/