JavaScript-垃圾回收与性能优化

本文介绍了JavaScript的垃圾回收机制,包括标记清理和引用计数策略,以及如何通过优化代码来配合垃圾回收,提升程序性能。同时,讨论了使用let和const、隐藏类优化及防止内存泄漏的方法,如避免意外全局变量、正确管理定时器和理解闭包的影响。
摘要由CSDN通过智能技术生成

JavaScript-垃圾回收与性能优化

垃圾回收

JavaScript 是使用垃圾回收的语言,也就是说 执行环境负责在代码执行时管理内存

当我们声明一个变量时,它会占用系统的内存。当我们不需要使用该变量时,该变量就成了“垃圾”,我们应该释放该变量所占用的内存,以优化网页的性能。

垃圾回收的基本思路就是,确定哪个变量不再使用了,就释放该变量所占的内存。这个过程是周期性的,比方说以某段时间为一个周期,或者当变量数量或占用的内存到达一定数值时,自动清理无用的变量,释放内存。

虽然 JavaScript 会自动回收“垃圾”,但这并不是完美的。可能存在某些无法被 JavaScript 识别的“垃圾”,这些“垃圾”大多是由程序员无意造成的,需要手动标记清理。

在浏览器的发展史上,主要有两种垃圾回收策略: 标记清理引用计数

标记清理

JavaScript 最常用的垃圾回收策略就是标记清理(mark-and-sweep)。

例如当变量进入上下文时,表明该变量正在使用,我们可以对它进行标记。标记的方法有很多,例如将它存入一个“正在使用”的数组。当变量离开上下文时,就表示该变量已经无用了,可以被销毁了,我们可以将它从“正在使用”的数组转移到“无用”的数组。当条件到达时,垃圾回收机制就会自动清理“无用”数组里的变量,并回收他们的内存。

引用计数

另一种策略就是引用计数(reference counting),并不常用。因为它无法识别出值比较“特别”的变量,导致无法回收内存。

它的思路就是对每个值都记录它被变量引用的次数。当一个变量被赋予一个引用类型的值时,那么该值的引用次数就 +1,若该变量被重新赋予一个新的值,切断了跟该值的引用关系,那么该值的引用次数就 -1。当该值的引用数量为 0 时,就表示该值可以被安全的回收了。

看似很合理,但是出现了一个严重的问题,就是引用计数无法处理 循环引用 的情况。

所谓的循环引用就是两个引用类型的值相互引用,如下代码:


const obj1 = new Object();	// 该 引用值1 被 obj1 引用 1次
const obj2 = new Object();	// 该 引用值2 被 obj2 引用 1 次

obj1.add = obj2;	// 引用值1 的 add 属性引用了 引用值2,导致 引用值2 的引用次数为 2
obj2.add = obj1;	// 引用值2 的 add 属性引用了 引用值1,导致 引用值1 的引用次数为 2

obj1 = null;	// 变量 obj1 被重新赋值,切断了跟 引用值1 的联系,引用值1 的引用次数 -1,为 1
obj2 = null;	// 变量 obj2 被重新赋值,切断了跟 引用值2 的联系,引用值2 的引用次数 -1,为 1
// 所以之前的引用值的引用次数都不为 0,无法正常回收内存

如果想切断变量与引用值之间的联系,就把变量设置为 null 即可。垃圾回收机制会自动清理值为 null 的变量。


obj1.add = null;
obj2.add = null;
// 像这样 引用值1 和 引用值2 的引用次数就为 0 了

性能优化

如果内存中存在许多变量,会影响程序的性能,所以需要靠垃圾回收机制来清理无用的变量。但是过于频繁的触发垃圾回收机制,不仅不能达到提升性能的目的,反而会造成反作用。所以垃圾回收机制的调度时机十分的重要。
当然垃圾回收的调度时机不是我们能决定的,是浏览器决定的。但是我们可以通过优化代码来配合垃圾回收,从而提升程序性能。

一般情况下,系统分配给浏览器的内存通常会比桌面程序少很多,如果是移动设备的浏览器,能分配到的内存就更少了。所以我们要保持内存的占用量在一个较小的范围。

优化内存占用最佳的手段就是保证执行代码时只保存必要的数据,无用的数据可以赋值为 null 解除引用 ,从而释放内存。

例如:


let obj = {
	username: "王五",
	password: "1234"
};

obj = null;	// 解除引用关系,释放内存

使用 let & const

let 和 const 是 ES6 新增的关键字,用于声明变量。
因为 let 和 const 都有块作用域,所以相比 var ,在某些场景下它们可以更早的被垃圾回收机制发现。例如,在块作用域比函数作用域更早终止的情况下。

多使用 const ,其次 let ,尽量不要使用 var ,更不要不声明就使用变量(因为该变量会提升为全局变量)。

共享隐藏类(V8)

Chrome 浏览器 V8 JavaScript 引擎将代码编译成实际的机器码时会使用“隐藏类”。
运行期间 V8 会将创建的对象与隐藏类关联起来,能够共享相同隐藏类的对象性能会更好。

例如:


function Person(name, age) {
	this.name = name;
	this.age = age;
}

let p1 = new Person("张三", 18);
let p2 = new Person("李四", 20);

根据以上代码,V8 会在后台配置,让 p1 和 p2 这两个实例共享相同的隐藏类。

动态添加或删除属性会导致不再共享相同的隐藏类,而是生成各自的隐藏类。

例如:


function Person(name, age) {
	this.name = name;
	this.age = age;
}

let p1 = new Person("张三", 18);
let p2 = new Person("李四", 20);

p2.gender = 0;	// 动态添加属性,应该避免!

或者


function Person(name, age) {
	this.name = name;
	this.age = age;
}

let p1 = new Person("张三", 18);
let p2 = new Person("李四", 20);

delete p2.age;	// 动态删除属性,应该避免!

所以我们要避险先创建实例,在补充属性的写法,应该在构造函数里一次性声明完所有的属性。

例如:


function Person(name, age, gender=0) {
	this.name = name;
	this.age = age;
	this.gender = gender;
}

let p1 = new Person("张三", 18);
let p2 = new Person("李四", 20, 0);

也不要使用 delete 表达式动态删除实例的属性。如果不需要该属性,可以给该属性赋予 null。这样既可以保持隐藏类不变并继续共享,又可以让无用的值被回收。

例如:


function Person(name, age) {
	this.name = name;
	this.age = age;
}

let p1 = new Person("张三", 18);
let p2 = new Person("李四", 20);

p2.age = null;	// 将不用的属性赋值为 null

防止内存泄漏

不好的 JavaScript 编码习惯可以能会出现难以察觉且有害的内存泄漏问题。JavaScript 中的内存泄漏大部分是不合理的引用造成的。

  • 意外的全局变量
  • 忘记关闭定时器
  • 不合理的闭包
意外的全局变量

在不使用关键字声明就使用变量,那么改变了会被提升为全局变量。


function getName() {
	myName = "王五";	// myName 未声明就使用!!!
}

解决方法就是给 myName 前面加上 const、let 或 var 关键字。

忘记关闭定时器

忘记关闭定时器也会悄悄泄露内存。


function run() {
	setIntervarl(() => {
		console.log("光阴似箭,日月如梭");
	}, 1000);
}

run();

解决方法就是在不需要定时器的时候关闭即可。


let timer;

function run() {
	timer = setIntervarl(() => {
		console.log("光阴似箭,日月如梭");
	}, 1000);
}

function stop() {
	clearInterval(timer);	// 关闭定时器
}

run();	// 开启定时器

stop();	// 关闭定时器

不合理的闭包

JavaScript 的闭包很容易不知不觉就造成内存泄漏。


function func1() {
	let a = "123";
	
	return function func2() {
		return a;
	}
}

let result = f1();	// 此时 result 引用了 func2 ,func2 引用了 func1 里的变量 a
					// 导致 func1 里的变量 a 无法被回收
					// 假设 a 里不是 "123" 而是内容很大的一个字符串,那就可能引发大问题了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值