JS内存管理和闭包

75 阅读4分钟

JS内存管理和闭包

某些编程语言需要我们自己手动管理内存(C、C++),有些会自动管理内存(Java、JS、Python)。

内存管理的生命周期:

  1. 分配申请你需要的内存
  2. 使用分配的内存
  3. 释放不需要的内存

JS对于内存的分配:

  • 基本数据类型在栈空间进行分配
  • 复杂数据类型的内存的分配会在堆内存中开辟一块空间,在栈内存存储指向该空间的指针

image.png

垃圾回收<Garbage Collection, GC>

当对象不再使用时,JS引擎会自动对其进行释放。如何释放的呢?主要有两种方式:①引用计数法; ②标记清除法;

引用计数法

存在问题:可能会出现循环引用循环引用

标记清除

设置一个根对象,垃圾回收器会定期从这个根开始,找到所有从根开始有引用的对象,没有引用到的对象,认为是不可用对象。至此,window对象共有两个别名:GO 和 VO

V8引擎采取的算法

为了进行更好的优化,V8引擎结合了多种算法:

  • 标记清除:主要采取的方法

  • 标记整理:与标记清除相似,不过在回收期间同时会将不会被删除的对象搬运到连续的内存空间,从而避免了内存碎片

  • 分代收集:

    1. 对象会被分为两组:新的和旧的。
    2. 由于很多对象被创建之后很快就会被销毁。我们将新对象放入一块内存区(新区),旧对象放入另一片内存区(旧区)。(对于新旧对象的判定其实很简单,在新内存区中,再次划分为两片区域,新创建的对象总是放入左边,经过下一轮标记清除,未被清除的对象将放入右边,再经过下一轮标记清除,依旧没有清除的对象将放入旧区。)
    3. 对于新对象标记清楚的检查频次高,对于新对象检查的频次低。
  • 增量收集:如果有很多对象,我们需要耗费好多时间(带来延迟)去遍历并标记整个对象集。所以引擎试图将垃圾收集工作分成几部分来做,这样会用多次微小的延迟代替一次一个大的延迟。

  • 闲时收集:垃圾收集器只会在CPU空闲时间内运行,以减少对代码执行的影响

事实上,为了提高内存的管理效率,对内存的划分非常详细:

image.png

  • Map space:存储一些隐藏类(如果我们创建了两个相同的对象,V8引擎会自动创建一个隐藏类)
  • Old pointer space:存储旧区的旧对象
  • Old data space:存储旧区的旧数据

闭包

闭包解释:

  • 闭包(closure)是一个函数以及其捆绑的周边环境状态(lexical environment词法环境)的引用的组合。
  • 闭包让开发者可以从内部函数访问外部函数的作用域。
  • 在 JavaScript 中,闭包会随着函数的创建而被同时创建。
  • 广义来说,JS的每个函数都会形成闭包;狭义来说,如果一个函数访问了外层作用域的遍历,那么会形成闭包

没有闭包的代码类似这样:

/*没有闭包的代码*/
var name = "why"
var age = 18
var height = 1.88
var address = "广州市"
var intro = "了解真相, 你才能获得真正自由!"
​
​
function foo(name, age, height, address, intro, num1, num2) {
    var message = "Hello World"
    console.log(message, name, age, height, address, intro)
​
    function bar() {
        console.log(name)
    }
​
    bar()
}
​
​
foo(name, age, height, address, intro, 20, 30)
/*有闭包的代码*/
var message = "Hello World"function foo() {
    console.log(message)
}
​

内存泄漏:

// function adder(num, count) {
//   return num + 5
// }
// add(100, 5)
// add(55, 5)
// add(12, 5)// add(22, 8)
// add(35, 8)
// add(7, 8)
​
function createAdder(count) {
    function adder(num) {
        return count + num
    }
​
    return adder
}
​
var adder5 = createAdder(5)
adder5(100)
adder5(55)
adder5(12)
​
var adder8 = createAdder(8)
adder8(22)
adder8(35)
adder8(7)
​
console.log(adder5(24))
console.log(adder8(30))
​
// 之后永远不会再使用adder8
// 内存泄漏: 对于那些我们永远不会再使用的对象, 但是对于GC来说, 它不知道要进行释放的对应内存会依然保留着

释放内存泄露:

    function createAdder(count) {
      function adder(num) {
        return count + num
      }
​
      return adder
    }
​
    var adder5 = createAdder(5)
    adder5(100)
    adder5(55)
    adder5(12)
​
    var adder8 = createAdder(8)
    adder8(22)
    adder8(35)
    adder8(7)
​
    console.log(adder5(24))
    console.log(adder8(30))
​
    // 永远不会再使用adder8
    // 内存泄漏: 对于那些我们永远不会再使用的对象, 但是对于GC来说, 它不知道要进行释放的对应内存会依然保留着
    adder8 = null
​

浏览器闭包内存的优化:会销毁闭包中用不到的属性