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交换

晋升

将新生代对象移动到老生代

  1. 一轮 GC 后还存活的对象进行晋升
  2. 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性能优化/
作者
tatekii
发布于
2022年3月31日
许可协议