Skip to content

管理内存

JavaScript 的内存管理主要包括两个方面:内存分配和垃圾回收。

  • 内存分配:在 JavaScript 中,当需要创建一个新对象时,会先申请一块内存空间来存储它。这个过程通常是由 JavaScript 引擎的“堆”(Heap)来负责的。堆是一种动态分配的内存空间,可以根据需要调整大小。除了堆之外,JavaScript 还有另一个内存区域——栈(Stack),用于存储执行上下文、变量等数据。不同于堆,栈空间的大小固定且较小,每个执行上下文(函数)都有自己的栈帧,保存着该函数的参数、内部变量等信息。
  • 垃圾回收:由于 JavaScript 采用了自动内存管理的方式,因此当一个对象不再被使用时,JavaScript 引擎会自动回收它所占用的内存空间。JavaScript 中的垃圾回收采用了标记清除(Mark and Sweep)的算法来实现。简单来说,就是通过遍历对象之间的引用关系,将所有可达的对象标记为“已使用”,然后将未标记的对象视为“垃圾”并回收它们所占的内存空间。

内存分配

内存分配主要涉及到引用类型和基本数据类型。大部分情况下引用类型是保存在堆内存中,而基本数据类型则通常保存在栈内存中。

栈是一种先进后出的数据结构,用于存储函数调用、局部变量等信息,其内存分配与回收由编译器或者虚拟机自动完成;而堆则是一种动态分配的内存空间,用于存储对象、数组、函数等复杂数据类型,其内存分配和释放需要由程序员手动管理。

js
let obj = {}

这里,obj 就是一个空对象,JavaScript 引擎会为其分配一段内存空间来存储它的属性和方法等信息。

当我们给对象添加属性时,JavaScript 引擎会根据属性的数据类型来分配内存空间。给 obj 对象添加一个字符串属性 name:

js
obj.name = 'Tom'

JavaScript 会为该属性分配一段内存空间来存储字符串“Tom”。

除了对象之外,JavaScript 还支持一些基本数据类型,比如数字、字符串、布尔值等。这些基本数据类型通常可以直接存储在栈空间中,不需要动态分配内存。

js
let str = 'hello'

这里,str 就是一个字符串变量,它会被直接存储在栈空间中,不需要额外的内存分配。

垃圾回收

JavaScript 采用自动垃圾回收机制,无法手动控制。它可以自动检测并回收程序中不再使用的内存空间。

主要的垃圾回收算法:标记清除和引用计数。

标记清除算法(Mark and Sweep Algorithm):最常用的 JavaScript 垃圾回收算法之一。通过标记“根对象”并遍历所有可达对象,将未被标记的对象进行清除。当 JavaScript 堆内存达到一定大小时,垃圾回收器就会开始工作。标记清除算法通常是在程序空闲或者系统资源空闲时执行。

引用计数算法(Reference Counting Algorithm):另一种比较简单的垃圾回收算法。原理是在内存中为每个对象维护一个引用计数器,每当有一个新的引用指向该对象,计数器就会增加 1;反之,每当一个引用被删除或者指向其他对象时,计数器就会减少 1。当计数器为 0 时,说明该对象已经没有被引用,即成为垃圾对象,可以被回收。引用计数算法触发时机是根据对象的引用计数来决定的。

数据清除和空间释放

数据清除和空间释放是相关但不完全等同的概念。

  • 数据清除指的是将原本存储在内存中的数据进行清除,使其不再被访问。例如,当一个函数执行完毕后,其中定义的局部变量等数据就会被自动清除。
  • 空间释放则是指将被清除的内存空间回收,以便其他对象可以使用。例如,当垃圾回收机制扫描堆内存中的对象时,会将所有已经没有被引用的对象进行标记,并将其所占用的内存空间进行回收。

优化建议

  • 使用对象池和循环利用对象:在代码中尽可能复用已有的对象,而不是频繁地创建和销毁对象。可以使用对象池等技术来实现对象的循环利用。
  • 及时释放不再使用的对象:当数据不再使用时,应该及时将其引用清除,并且通过 delete 操作符彻底删除对象属性等数据,以便垃圾回收机制可以及时回收这些无用的内存空间。
  • 避免一次性创建过多的数据:如果需要处理大量数据,应该避免一次性创建过多的数据对象,而是采用逐步处理的方法,分批加载和处理数据。
  • 减少闭包的使用:闭包会导致函数作用域中的变量无法被及时释放,从而占据较多的内存空间。因此,应该尽可能减少闭包的使用,或者在使用闭包时注意及时释放其中占用的变量等资源。
  • 使用事件委托等技术:事件委托、虚拟滚动等技术可以有效地减少页面中 DOM 元素的数量,从而降低内存消耗。