JS的内存管理和闭包

441 阅读4分钟

学习了coderwhy的JavaScript高级语法视频课的笔记

如有错误或者不合适的地方,敬请见谅,欢迎指出和拓展,谢谢各位了

一、认识内存管理

  1. 不管什么样的编程语言,在代码的执行过程中都是需要给它分配内存的,不同的是某些编程语言需要我们自己手动的管理内存,某些编程语言会可以自动帮助我们管理内存;
  2. 不管以什么样的方式来管理内存,内存的管理都会有如下的生命周期
    • 第一步:分配申请你需要的内存(申请);
    • 第二步:使用分配的内存(存放一些东西,比如对象等);
    • 第三步:不需要使用时,对其进行释放
  3. 不同的编程语言对于第一步和第三步会有不同的实现:
    • 手动管理内存:比如C、C++,包括早期的OC,都是需要手动来管理内存的申请和释放的(malloc和free函 数);
    • 自动管理内存:比如Java、JavaScript、Python、Swift、Dart等,它们有自动帮助我们管理内存

二、JS的内存管理

JavaScript会在定义变量时自动为我们分配内存。内存分配复杂,我们可以从这主要的两块去了解:

  • JS对于基本数据类型内存的分配会在执行时,直接在栈空间进行分配;
  • JS对于复杂数据类型内存的分配会在堆内存中开辟一块空间,并且将这块空间的指针返回值变量引用

三、垃圾回收机制

  1. 内存的大小是有限的,所以当内存不再需要的时候,我们需要对其进行释放,以便腾出更多的内存空间。
  2. 手动管理内存的语言中,我们需要通过一些方式自己来释放不再需要的内存,比如free函数:
  • 这种管理的方式其实非常的低效,影响我们编写逻辑的代码的效率;
  • 这种方式对开发者的要求也很高,并且一不小心就会产生内存泄露
  1. 所以大部分现代的编程语言都是有自己的垃圾回收机制:
  • 垃圾回收的英文是Garbage Collection,简称GC
  • 对于那些不再使用的对象,我们都称之为是垃圾,它需要被回收,以释放更多的内存空间;
  • 而我们的语言运行环境,比如Java的运行环境JVM,JavaScript的运行环境js引擎都会内存垃圾回收器
  • 垃圾回收器我们也会简称为GC,所以在很多地方你看到GC其实指的是垃圾回收器;
  1. GC的算法 常见的GC算法有两种:引用计数标记清除
  • 引用计数:当一个对象有一个引用指向它时,那么这个对象的引用就+1,当一个对象的引用为0时,这个对象就可以被销 毁掉。但是这个算法有一个很大的弊端就是会产生循环引用,就是两个对象相互引用,难以销毁。
  • 标记清除:这个算法是设置一个根对象(root object),垃圾回收器会定期从这个根开始,找所有从根开始有引用到的对象,对于哪些没有引用到的对象,就认为是不可用的对象,就会进行销毁。JS引擎比较广泛的采用的就是标记清除算法,当然类似于V8引擎为了进行更好的优化,它在算法的实现细节上也会结合 一些其他的算法。

四、闭包的定义

  1. 一个普通的函数function,如果它可以访问外层作用于的自由变量,那么这个函数就是一个闭包;
  2. 广义的角度来说:JavaScript中的函数都是闭包;
  3. 狭义的角度来说:JavaScript中一个函数,如果访问了外层作用于的变量,那么它是一个闭包(更推荐这种说法)。也就是一个内部函数引用外部函数的变量,当外部函数执行时,闭包就存在了。
<script>
        function foo () {
        
            // start
            var a = 10
            function bar () {
                console.log(a)
            }
            return bar
            // end
            
        }

        var fn = foo()
        fn()
</script>

五、闭包的内存泄露

之所以存在闭包的内存泄露就是因为本应该销毁成为垃圾的对象而并没有销毁。 我们以这个为例(要有执行上下文基础):

<script>
        function foo () {
        
            // start
            var a = 10
            function bar () {
                console.log(a)
            }
            return bar
            // end
            
        }

        var fn = foo()
        fn()
</script>

image.png 执行foo函数,a赋值为10,返回bar函数地址值赋给fn,此时GO的fn就指向bar的存储函数空间,到此为止foo执行完毕,foo的FEC函数执行上下文栈退出ECS执行上下文栈。按理foo的AO应该为垃圾进行销毁,但是GO的fn就指向bar的存储函数空间,也就是从GO找得到,所以标记清除不能销毁。然后执行fn()就是执行bar函数,当console.log(a)时,当前作用域无a,则向父级作用域查找。所以造成了内存泄露,AO没有销毁。如需销毁,这需要fn=null和foo=null来释放,使GO不再指向其它foo和bar的存储函数空间,JS引擎会的GC定时清除。