>  기사  >  웹 프론트엔드  >  JS 시리즈(3)의 가비지 수집 메커니즘, 메모리 누수, 클로저를 종이 한 장으로 이해하기

JS 시리즈(3)의 가비지 수집 메커니즘, 메모리 누수, 클로저를 종이 한 장으로 이해하기

coldplay.xixi
coldplay.xixi앞으로
2020-09-30 16:38:121950검색

javascript 칼럼에서는 가비지 수집 메커니즘, 메모리 누수 및 클로저 내용을 모두에게 소개합니다.

JS 시리즈(3)의 가비지 수집 메커니즘, 메모리 누수, 클로저를 종이 한 장으로 이해하기

Written at the front: 이것은 주로 프레임워크가 만연한 시대에 javascript 칼럼에 쓰기 시작하는 시리즈입니다. 업무용, 인터뷰용, 기술 발전용으로 프레임워크를 사용하지만, JS의 기본 지식은 금상첨화이며 꼭 배워야 할 지식이기도 합니다. 자동차를 운전하는 사람은 자동차에 대해 많이 알 필요는 없지만 자동차의 일반적인 기능만 익히면 됩니다. . 하지만 자동차를 안다면 운전을 더 잘할 수 있습니다. 물론, 기사는 단지 하나의 지식 포인트에 대해서만 이야기하는 것이 아닙니다. 일반적으로 관련된 지식 포인트가 시리즈로 연결되어 자신의 학습을 기록하면서 자신의 학습을 공유하고 서로 격려하게 됩니다! 가능하시다면 좋아요도 부탁드려요, 여러분의 좋아요 덕분에 저도 업데이트하는데 더 힘이 됩니다!

개요

  • 식사 시간: 6~12분
  • 난이도: 쉬움, 뛰지 말고 다 읽을 때까지 기다리세요

쓰레기 수집 메커니즘

이전 블로그에서는 주로 메모리(스택 메모리 및 힙 메모리, 딥 카피 및 얕은 카피)는 물론 사용 후에는 사용하지 않은 메모리를 반환해야 합니다. 마치 휴대폰에서 사용하지 않는 소프트웨어를 백그라운드에서 삭제하는 것과 마찬가지로 실행 속도를 향상시킬 수 있습니다. 그렇지 않으면 더 많이 사용할수록 조만간 정체될 것입니다. JS도 마찬가지입니다. JS 也是一样的。

每隔一段时间, JS垃圾收集器都会对变量进行“巡逻”,就和保安巡逻园区一样,让不相干的人赶紧走。当一个变量不被需要了以后,它就会把这个变量所占用的内存空间所释放,这个过程就叫做垃圾回收

JS 的垃圾回收算法分为两种,引用计数法和标记清除法

  • 引用计数法

    引用计数法是最初级的垃圾回收算法,已经被现代浏览器所淘汰了。在学习引用计数法之前,需要首先对引用有一定的概念,你可以认为它就是对当前变量所指向的那块内存地址的描述,有点类似于JS引用数据类型的内存指向的概念,先来看一行代码:

    var obj={name:'jack'};复制代码

    当我们在给 obj 赋值的同时,其实就创建了一个指向该变量的引用,引用计数为1,在引用计数法的机制下,内存中的每一个值都会对应一个引用计数

    而当我们给 obj 赋值为 null时,这个变量就变成了一块没用的内存,那么此时, obj 的引用计数将会变成 0,它将会被垃圾收集器所回收,也就是 obj 所占用的内存空间将会被释放

    我们知道,函数作用域的生命周期是很短暂的,在函数执行完毕之后,里面的变量基本是没用的变量了,不清除的后果就是该内存垃圾没有被释放,依然霸占着原有的内存不松手,就会容易引发内存泄漏,先来看一段代码以及运行结果:

    function changeName(){   var obj1={};   var obj2={};
       
       obj1.target=obj2;
       obj2.target=obj1;
       obj1.age=15;   console.log(obj1.target);   console.log(obj2.target);
    }
    
    changeName();复制代码
    JS 시리즈(3)의 가비지 수집 메커니즘, 메모리 누수, 클로저를 종이 한 장으로 이해하기
    我们可以看到, obj1.targetobj2.target 存在互相引用的情况,因为在改变 obj1.age 的同时,obj1.target.ageobj2.target.age 也同时都被影响到了,它们所指向的引用计数是一致的

    在函数执行完毕的时候, obj1obj2 还是活的好好地,因为 obj1.targetobj2.target가끔 JSGarbage Collector는 공원을 순찰하는 경비원처럼 변수를 "순찰"하여 관련 없는 사람들을 빠르게 떠나게 합니다. 변수가 더 이상 필요하지 않으면 변수가 차지하는 메모리 공간을 해제합니다. 이 프로세스를 garbage collection

  • JS의 가비지 수집 알고리즘은 두 가지 유형으로 나누어집니다. 및 마크 지우기

    참조 카운팅 참조 카운팅은 가장 기본적인 가비지 수집 알고리즘이며 최신 브라우저에서 제거되었습니다. 참조 카운팅 방법을 배우기 전에 먼저 reference라는 개념을 알아야 합니다. 현재 변수가 가리키는 메모리 주소에 대한 설명이라고 생각하면 됩니다. JS 참조 데이터 유형 먼저 코드 줄을 살펴보겠습니다.
    function changeName(){    var obj1={};  var obj2={};
      
      obj1.target=obj2;
      obj2.target=obj1;
      obj1.age=15;  console.log(obj1.target);  console.log(obj2.target);
    }
    
    changeName();复制代码
    obj에 값을 할당하면 실제로 참조 개수가 1인 변수를 가리키는 reference
      가 생성됩니다. ,
    • 참조 카운트 메커니즘에 따라 메모리의 각 값은 참조 카운트에 해당합니다

      그리고 objnull에 할당하면 이 변수는 쓸모없는 메모리 조각, 그러면 이때 obj의 참조 횟수는 0이 되고 가비지 컬렉터에 의해 재활용됩니다. 즉, obj가 차지하는 메모리 공간입니다. 가 출시됩니다

    • 함수 범위의 수명주기는 매우 짧습니다. 함수가 실행된 후 내부 변수는 기본적으로 쓸모없는 변수입니다. 이를 지우지 않으면 메모리 쓰레기가 됩니다. 해제되지 않고 여전히 사용 중입니다. 원래 메모리를 버리지 않으면
    • 메모리 누수

      가 발생하기 쉽습니다. 먼저 코드 일부와 실행 결과를 살펴보겠습니다.

       function f1(){     var n=999;
      
           nAdd=function(){n+=1}     function f2(){         console.log(n);
           }     return f2;
      
       } var result=f1();     //等同于return f2();
      
       result(); // 999
      
       nAdd();
      
       result(); // 1000
       nAdd();
      
       result(); // 1000复制代码
      JS 시리즈(3)의 가비지 수집 메커니즘, 메모리 누수, 클로저를 종이 한 장으로 이해하기
      obj1.targetobj2.target이 서로를 참조하는 것을 볼 수 있습니다. obj1.age일 때 >가 변경되면 obj1.target.ageobj2.target.age도 동시에 영향을 받습니다. 실행 시 obj1obj2는 여전히 살아 있습니다. obj1.targetobj2.target의 참조 카운트 때문입니다. > 실행 후에도 여전히 1

      , 분명히 함수가 실행되었지만 이런 쓰레기가 여전히 존재합니다. 이러한 함수가 너무 많으면 🎜메모리 누수🎜가 불가피합니다🎜🎜🎜🎜마크 및 지우기 방법🎜🎜 위의 참조 카운팅 방법의 단점은 이미 명백합니다. 그러면 지금 우리가 말하는 마크 앤 클리어 방법에는 그러한 문제가 없습니다. 판단 기준은 객체 🎜가 도달 가능 🎜인지 확인하는 것이므로 주로 🎜 표시 단계 🎜와 🎜 삭제 단계 🎜의 두 단계로 나뉩니다. 🎜🎜🎜🎜 표시 단계 🎜🎜 가비지 수집기는 다음에서 시작됩니다. 루트 객체(Window 객체) 및 도달 가능한 모든 객체를 검색하는 것이 소위 🎜reachable🎜🎜🎜🎜🎜제거 단계입니다. 스캔하는 동안 루트 개체가 건드릴 수 없는 개체(🎜unreachable🎜)는 불필요한 개체로 간주되어 가비지로 삭제됩니다🎜

    现在再来看下上面的代码

    function changeName(){    var obj1={};  var obj2={};
      
      obj1.target=obj2;
      obj2.target=obj1;
      obj1.age=15;  console.log(obj1.target);  console.log(obj2.target);
    }
    
    changeName();复制代码

    在函数执行完毕之后,函数的声明周期结束,那么现在,从 Window对象 出发, obj1obj2 都会被垃圾收集器标记为不可抵达,这样子的情况下,互相引用的情况也会迎刃而解。

内存泄漏

该释放的内存垃圾没有被释放,依然霸占着原有的内存不松手,造成系统内存的浪费,导致性能恶化,系统崩溃等严重后果,这就是所谓的内存泄漏

闭包

  • 定义与特性

    闭包是指有权访问另一个函数作用域中的变量的函数。至于为什么有权访问,主要是因为作用域嵌套作用域,也就是所谓的作用域链,关于作用域链不清楚的可以看我的第一篇博客一文搞懂JS系列(一)之编译原理,作用域,作用域链,变量提升,暂时性死区,就是因为作用域链的存在,所以内部函数才可以访问外部函数中定义的变量 ,作用域链是向外不向内的,探出头去,向外查找,而不是看着锅里,所以外部函数是无法访问内部函数定义的变量的。并且,还有一个特性就是将闭包内的变量始终保持在内存中。

    前面的作用域向外不向内,这里就不再做过多解释了,我们主要来看我后面说的特性,那就是闭包内的变量始终保存在内存中

    来看一下阮一峰教程当中的一个例子

     function f1(){     var n=999;
    
         nAdd=function(){n+=1}     function f2(){         console.log(n);
         }     return f2;
    
     } var result=f1();     //等同于return f2();
    
     result(); // 999
    
     nAdd();
    
     result(); // 1000
     nAdd();
    
     result(); // 1000复制代码

    从输出结果就可以看得出来,这个变量 n 就一直保存在内存中,那么,为什么会这样子呢,我们现在就来逐步地分析代码

    ① 首先 f1() 作为 f2() 的父函数,根据作用域链的规则, nAdd() 方法以及 f2() 方法中可以正常访问到 n 的值

    f2() 被赋予了一个全局变量,可能这里大家就会开始产生疑惑了,这个 f2() 不是好好地定义在了 f1() 函数中吗,这不是扯淡吗,那么,先看下面的这句 var result=f1(); ,这个 result 很明显是被赋予了一个全局变量,这应该是没有任何争议的,那么,接着来看这个 f1() ,可以看到最后,是一句 return f2; ,看到这里,想必大家也已经想明白了,这个 f2() 被赋予了一个全局变量

    ③ 已经明白了上面的这一点以后,根据上面垃圾回收机制所提及到的标记清除法,这个 f2() 始终是可以被根对象 Window 访问到的,所以 f2 将始终存在于内存之中,而 f2 是依赖于 f1 ,因此 f1 也将始终存在于内存当中,那么, n 的值也就自然始终存在于内存当中啦

    ④ 还有一点需要注意的就是为什么我们可以直接执行 nAdd() ,这是因为在 nAdd() 的前面没有使用 var ,因此 nAdd() 是一个全局函数而不是局部函数

    所以,闭包的变量会常驻内存,滥用闭包容易造成内存泄漏,特别是在 IE 浏览器下,2020年了,应该没人使用 IE 了吧(小声bb),解决办法就是在退出函数之前,将不使用的局部变量全部删除,这也是上面讲了垃圾回收机制 => 内存泄漏,再讲到闭包的原因,我会尽量将有关联性的知识点一起讲了,也方便大家学习和加深印象。

系列目录

相关免费学习推荐:javascript(视频)

위 내용은 JS 시리즈(3)의 가비지 수집 메커니즘, 메모리 누수, 클로저를 종이 한 장으로 이해하기의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 juejin.im에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제