[JS基础回顾] 闭包 又双叒叕来~~~

694 阅读5分钟

目录

  1. 闭包的解释
  2. 闭包暴露``函数作用域3种方式
  3. 关于闭包的内存泄露
  4. 循环和闭包

一 闭包的解释

闭包是基于词法作用域书写代码时所产生的自然结果,你甚至不需要为了利用它们而有意识地创建闭包

MDN的解释闭包是函数声明该函数的词法环境的组合。

Tips: 词法作用域词法环境 1,此时函数还没被执行,所以使用的是词法作用域即静态作用域.2, 此时函数被执行,此时词法作用域就会变成词法环境(包含静态作用域与动态作用域)

以上的解释 个人感觉还是不够清晰 我这样理解

  1. 闭包就是突破了函数作用域
  2. 闭包就是函数嵌套函数, 子函数可以访问父函数的变量(也就是所谓的自由变量), 所以,此变量不会被回收.

闭包暴露``函数作用域3种方式:

1) 通过外部函数的参数进行暴露

闭包内 调用外部函数 通过外部函数的参数 暴露 闭包内 自由变量.

function fn() { 
  var a = 2;
  function innerFn() { 
   outerFn(a) //通过外部函数的参数进行暴露
  }
  innerFn(); 
};
function outerFn(val) { 
   console.log(val); // 2
}
fn(); // 2

2) 通过外部作用域的变量进行暴露

其中val为全局变量

function fn() { 
  var a = 1;
  function innerFn() { 
      val = a; //通过外部作用域的变量进行暴露
  }
  innerFn(); 
};

fn();
console.log(val); // 1

3) 通过return直接将整个函数进行暴露

function fn() { 
  var a = 1;
  function innerFn() { 
      console.log(a);
  }
  return innerFn; //通过return直接将整个函数进行暴露
};

let a = fn();
a(); // 1

关于闭包的内存泄露

首先必须声明一点:使用闭包并不一定会造成内存泄露,只有使用闭包不当才可能会造成内存泄露.

为什么闭包可能会造成内存泄露呢?原因就是上面提到的,因为它一般会暴露自身的作用域给外部使用.如果使用不当,就可能导致该内存一直被占用,无法被JS的垃圾回收机制回收.就造成了内存泄露.

注意: 即使闭包里面什么都没有,闭包仍然会隐式地引用它所在作用域里的所用变量. 正因为这个隐藏的特点,闭包经常会发生不易发现的内存泄漏问题.

常见哪些情况使用闭包会造成内存泄露:

    1. 使用定时器未及时清除.因为计时器只有先停止才会被回收.所以决办法很简单,将定时器及时清除,并将造成内存的变量赋值为null(变成空指针)
    1. 相互循环引用.这是经常容易犯的错误,并且也不容易发现.
    1. 闭包引用到全局变量上.因为全局变量是只有当页面被关闭的时候才会被回收.

四 循环和闭包

1) 同步循环打印 正确的值

for (var i=1; i<5; i++) { 
    console.log( i );
}
// 1 2 3 4

2) 同步中嵌套异步任务(中的宏任务)循环打印 错误的值

当执行 console 时, 循环已经完成, 同步任务执行完成后,执行宏任务,此时 i 已经是 5.所以打印5个5.

for (var i=1; i<5; i++) { 
  setTimeout( function timer() {
    console.log( i );
  }, i*1000 );
}
// 打印出 5 个 5 

3) 创造5个独立的函数作用域,但是 i 也全都是对外部作用域的引用 错误的值

它的最终值仍然是5个5.为什么?我们来分析下,它用了一个匿名函数包裹了定时器,并立即执行.在进行for循环时,会创造5个独立的函数作用域(由匿名函数创建的,因为它是闭包函数).但是这5个独立的函数作用域里的i也全都是对外部作用域的引用.即它们访问的都是i的最终值5.这并不是我们想要的,我们要的是5个独立的作用域,并且每个作用域都保存一个"当时"i的值.

for (var i=1; i<5; i++) { 
  (function() {
    setTimeout( function timer() { 
	  console.log( i );
    }, i*1000 );
  })();
}
// 打印出 5 个 5 

4) 通过匿名函数创建独立的函数作用域,并且通过 变量 保存独立的 i 值

for (var i=1; i<5; i++) { 
  (function () {
    var x=i;
    console.log(x*1000); // 1000 2000 3000 4000
    setTimeout( function timer() { 
	  console.log( x );
    }, x*1000 );
  })();
}

// 1 2 3 4

5) 通过匿名函数创建独立的函数作用域,并且通过 参数 保存独立的 i 值

for (var i=1; i<5; i++) { 
  (function (x) {
    console.log(x*1000); // 1000 2000 3000 4000
    setTimeout( function timer() { 
	  console.log( x );
    }, x*1000 );
  })(i);
}

// 1 2 3 4

参考

注意

  • 使用定时器未及时清除.因为计时器只有先停止才会被回收.所以决办法很简单,将定时器及时清除,并将造成内存的变量赋值为null(变成空指针)
  • 闭包引用到全局变量上.因为全局变量是只有当页面被关闭的时候才会被回收.
  • 闭包就是函数嵌套函数, 子函数可以访问父函数的变量(也就是所谓的自由变量), 所以,此变量不会被回收.
  • 另外,闭包被问到,一定要说到作用域 与 执行上下文, AO VO 对象.可参考我的另一文章, 【JS引擎】解析过程