什么是闭包,闭包造成的内存泄露如何解决

373 阅读4分钟

一、什么是闭包

闭包就是一个函数,能够访问其他函数内部变量的函数。

详细来说,闭包就是在一个函数A内部定义一个新的函数B,并且这个新函数B,调用了在函数A内定义的变量,并且在函数A外部被调用,这就形成了闭包。

示例:

function outer() {
      var  a = '变量1'
      var  inner = function () {
             console.info(a)
      }
      return inner    
      // inner 就是一个闭包函数,因为他能够访问到outer函数的作用域
 }
 var  inner = outer()   // 获得inner闭包函数
 inner()   //"变量1"

当程序执行完var inner = outer(),其实outer的执行环境并没有被销毁,因为他里面的变量a仍然被inner函数作用域链所引用,当程序执行完inner(), 这时候,inner和outer的执行环境才会被销毁调;

二、闭包的优缺点

优点

  • 访问其他函数内部变量

    闭包可以访问定义时所处的词法作用域中的变量,即使外部函数已经执行完毕。

  • 延长变量的生命周期

    变量长期驻扎在内存中,不会被内存回收机制回收

  • 保护变量

    通过闭包,可以创建私有变量,只能通过内部函数访问,外部无法直接访问或修改(只能通过特定的get、set方法进行获取或修改) ,可以避免定义全局变量所造成的污染

缺点

  • 常驻内存,增加内存使用量
  • 使用不当造成内存泄漏

示例

变量私有化

function getName() {
      let name = '小花';
      
      function get(){
          return name;
      }
      
      function set(newName){
          name = newName
      }
      
      return {
          get:get,
          set:set
       }
 }
 
 let obj = getName() // 打印出get、set两函数
 let myName = getName.get();  // '小花'
 getName.set('小麦');  // 修改name为小麦

三、内存泄漏(内存浪费)

内存泄漏是指在程序运行时,分配的内存没有被正确释放,导致内存空间的浪费,最终可能会使程序崩溃或运行缓慢。

四、如何避免闭包引起的内存泄漏

  • 避免滥用闭包

    过多的闭包会导致代码难以理解和维护,同时也会增加内存开销。

  • 及时释放对闭包的引用

    在不再需要闭包时,应该及时将闭包的引用释放掉,以便垃圾回收器回收内存。

  • 避免闭包中的循环引用

    当闭包中包含循环引用时,会导致内存泄漏

  • 使用缓存机制

    在一些情况下,可以使用缓存机制来避免重复创建闭包。例如,可以使用一个对象来缓存已经创建的闭包,并在需要时进行复用。

示例

1、在定时器不被使用的时候及时清除定时器

 const count = '123123'
 var intervalId = setInterval(function () {
     console.log('count',count)
 }, 1000);
 //
 // clearInterval(intervalId);

2、在退出函数之前,将不使用的局部变量赋值为null

  这段代码会导致内存泄露
     window.onload = function(){
         var el = document.getElementById("id");
         el.onclick = function(){
             alert(el.id);
         }
     }
     解决方法为
     window.onload = function(){
         var el = document.getElementById("id");
         var id = el.id;                                      
         //解除循环引用
         el.onclick = function(){
             alert(id); 
         }
         el = null;                                          
         // 将闭包引用的外部函数中活动对象清除
     }

五、闭包常见的应用场景

  • 柯里化函数
  • 通过闭包实现变量/方法的私有化
  • 自执行函数(IIFE)
  • 缓存一些结果
  • 定时器setTimeOut

示例:

1、柯里化函数

为了避免频繁地调用具有相同参数的函数,可以将一个多参数的函数转化为一个单参数的函数,其实就是一个高阶函数

 //普通函数
 function getArea(w,h){
     return w * h;
 }
 const area1 = getArea(10,20);
 const area2 = getArea(10,30);
 const area3 = getArea(10,40);
 ​
 //柯里化函数
 function getArea(w){
     return function(h){
         return w * h;
     }
 }
 // 调用1
 getArea(10)(20)
 // 调用2
 const getTenArea = getArea(10);
 const area1 = getTenArea(20);
 const area2 = getTenArea(30);
 const area3 = getTenArea(40);
 ​

2、通过闭包实现变量/方法的私有化

 function funOne(i){
     function getTwo(){
         console.log('参数:', i)
     }
     return getTwo;
 }
 const fa = funOne(100); 
 const fb = funOne(200); 
 const fc = funOne(300); 

3、匿名自执行函数

 var funOne = (function(){
     var num = 0;
     return function(){
         num++;
         return num;
     }
 })()
 ​
 console.log(funOne());   // 1
 console.log(funOne());   // 2
 console.log(funOne());   // 3 
 ​

4、缓存一些结果

比如:外部函数定义一个变量,内部函数可以获取或修改这个变量的值,从而就延长了这个变量的生命周期

 function parent(){
     let list = [];
     function son(i){
         list.push(i);
     }
     return son;
 }
 ​
 const fn = parent();
 ​
 fn(1);
 fn(2);
 fn(3);