本文深入解析JavaScript中栈与堆的内存分配差异,阐述基本类型与引用类型的存储及传递方式。重点介绍浏览器垃圾回收的标记清除与引用计数原理,分析循环引用导致的内存泄漏问题,并提供数组、对象及函数的优化策略,帮助开发者有效管理内存。
一、栈(stack)和堆(heap)
栈(Stack)即栈内存,它是自动分配且大小相对固定的内存区域,由系统自动进行释放。栈遵循先进后出(FILO)的原则,其数据结构类似于乒乓球盒:先放入的球必须最后取出。

堆(Heap)即堆内存,它用于动态分配内存,大小不固定且不会自动释放。堆的数据结构是一种无序的树状结构,采用键值对(key-value)的方式存储。只要知道键名(key),就能通过它查找到对应的值(value)。这类似于书架存书,知道书名即可找到书籍。

栈的特点是开口向上、访问速度快但容量较小;堆的特点是访问速度稍慢,但容量较大。

二、基本类型和引用类型

基本数据类型包括Undefined、String、Boolean、Null和Number,它们直接按值存储在栈内存中。由于占用的内存空间大小确定,系统会自动分配和释放,这使得内存能够及时回收,相比堆内存更易于管理。引用数据类型主要指由多个值构成的对象,如对象(Object)、数组(Array)和函数(Function)。它们通过拷贝或new关键字创建,数据存储于堆中。
三、传值和传址的区别
基本类型采用值传递方式;引用类型则采用地址传递方式。
引用类型的数据地址指针存储在栈中,将栈内存中的地址赋值给接收变量。当访问引用类型的值时,需先从栈中获取对象的地址指针,再通过该指针找到堆中保存的实际数据(变量实际保存的是指向对象的指针,而非对象本身)。
四、内存分配与垃圾回收
1. 内存分配: (1)栈内存:采用线性有序存储,容量小,系统分配效率高。 (2)堆内存:需先在堆中分配存储区域,再将指针存入栈中,效率相对较低。
2. 垃圾回收: (1)栈内存:变量使用完毕后即被回收,存取速度快,且数据可共享。 (2)堆内存:对象不会随方法结束而销毁,即使方法执行完毕,若其他引用变量仍引用该对象(如参数传递),对象将继续存在。创建对象通常成本较高,旨在反复利用,因此保存在运行时数据区(堆内存)。只有当对象没有任何引用变量指向它时,垃圾回收机制才会核实并回收其内存。
五、浏览器垃圾回收机制
1. 浏览器的垃圾回收机制
(1). 垃圾回收的概念 垃圾回收是指JavaScript代码运行时,需分配内存空间存储变量和值。当变量不再参与运行时,系统收回被占用的内存空间,这一过程即为垃圾回收。
回收机制: ● JavaScript具有自动垃圾回收机制,会定期释放不再使用的变量和对象所占用的内存,原理是找到不再使用的变量并释放其内存。 ● JavaScript中存在局部变量和全局变量。全局变量生命周期持续至页面卸载;局部变量声明在函数中,生命周期从函数执行开始到结束。在此过程中,局部变量在堆或栈中存储值,函数执行结束后,局部变量不再被使用,其空间被释放。 ● 然而,当局部变量被外部函数使用时(如闭包情况),函数执行结束后,外部变量仍指向内部局部变量,此时局部变量仍被使用,因此不会被回收。
(3) 垃圾回收的方式 浏览器通常使用两种垃圾回收方法:标记清除和引用计数。 (1)标记清除
● 标记清除是浏览器常见的垃圾回收方式。当变量进入执行环境时,标记为“进入环境”,此时不能被回收,因为它们正在被使用。当变量离开环境时,标记为“离开环境”,此时内存可被释放。
● 垃圾收集器运行时,会给内存中所有变量加上标记。然后,去掉环境中的变量以及被环境中的变量引用的标记。在此之后仍被加上标记的变量被视为准备删除的变量,因为环境中的变量已无法访问它们。最后,垃圾收集器完成内存清除工作,销毁带标记的值并回收其内存空间。 (2)引用计数 ● 另一种垃圾回收机制是引用计数,使用相对较少。引用计数跟踪记录每个值被引用的次数。当声明一个变量并将一个引用类型赋值给该变量时,该值的引用次数为1。相反,如果包含对该值引用的变量又取得了另一个值,则该值的引用次数减1。当引用次数变为0时,说明该变量已无价值,因此在垃圾回收期下次运行时,该变量所占内存空间将被释放。
● 这种方法会引起循环引用问题:例如,obj1和obj2通过属性相互引用,两个对象的引用次数均为2。当使用循环计数时,由于函数执行完后,两个对象都离开作用域,函数执行结束,obj1和obj2仍将继续存在,因此它们的引用次数永远不会是0,从而引起循环引用。
function fun() {
let obj1 = {}
let obj2 = {}
obj1.a = obj2 // obj1 引用了 obj2
obj2.a = obj1 // obj2 引用了 obj1
}这种情况下,就要手动释放变量占用的内存:
obj1.a = null
obj2.a = null(3) 减少垃圾回收 虽然浏览器可以进行自动垃圾回收,但当代码比较复杂时,垃圾回收带来的代价较大,因此应尽量减少垃圾回收。 ● 对数组进行优化:清空数组时,最简单的方法是赋值为[],但这会创建一个新的空对象。可以将数组的长度设置为0,以此达到清空数组的目的。 ● 对object进行优化:对象尽量复用,对于不再使用的对象,将其设置为null,以便尽快被回收。 ● 对函数进行优化:在循环中的函数表达式,如果可以复用,尽量放在函数的外面。
2. 哪些情况会导致内存泄漏
以下四种情况会造成内存泄漏: ● 意外的全局变量:由于使用未声明的变量,意外创建了一个全局变量,使该变量一直留在内存中无法被回收。 ● 被遗忘的计时器或回调函数:设置了setInterval定时器,而忘记取消它。如果循环函数有对外部变量的引用,那么这个变量会一直留在内存中,无法被回收。 ● 脱离DOM的引用:获取一个DOM元素的引用,而后该元素被删除。由于一直保留对该元素的引用,所以它也无法被回收。 ● 闭包:不合理的利用闭包,导致某些变量一直被留在内存当中。
到此这篇关于前端高频面试题之JS中堆和栈的区别和浏览器的垃圾回收机制的文章就介绍到这了,更多相关js堆和栈的区别内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!