JavaScript闭包浅入浅出理解

139 阅读4分钟

一、什么是闭包?

MDN对闭包的解释

关键词(函数、词法环境、引用、组合)

函数

ES5规范中JavaScript仅有全局作用域和函数作用域(ES6增加块级作用域),根据作用域链,函数内部的代码可以访问外部声明的变量,但是外部代码无法访问函数内部的变量。换句话说如果你定义了一个叫Outer的函数,在Outer函数内部又定义了一个Inner函数,那么inner函数就可以访问Outer函数中声明的变量。

词法环境

词法环境涉及到JavaScript的执行上下文,这里不展开,只需要知道每个函数都具有自己的执行上下文即可。在JS执行上下文中,就包含了词法环境和变量环境。

引用、组合

还是用Outer函数和Inner函数说明,当Inner函数有代码访问Outer函数声明的变量时,就形成了引用,那么Outer函数和Inner函数就是一个闭包组合。

二、闭包有什么作用?

  1. 突破作用域链,使得外部代码可以访问函数内部代码
  2. 被引用的变量在其定义的函数执行完毕后不会被回收,仍在内存中

图中看到f1函数把内部函数f2 return出去,保存在一个全局变量result中,此时,只要执行result函数,就能访问到f1函数中定义的局部变量n了,并且说明了f1函数执行完毕后,局部变量n没有被回收掉。

三、闭包的最佳利用

模拟对象的私有属性

var Counter = (function() {
  var privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
      changeBy(-1);
    },
    value: function() {
      return privateCounter;
    }
  }
})();

console.log(Counter.value()); /* logs 0 */
Counter.increment();
Counter.increment();
console.log(Counter.value()); /* logs 2 */
Counter.decrement();
console.log(Counter.value()); /* logs 1 */

用一个立即执行函数(IIFE)返回一个对象保存在Counter变量中,该对象向外部提供了3个方法来访问和操作对象的私有属性,increment和decrement方法通过changeBy闭包函数来修改私有属性privateCounter的值,value方法则用来获取privateCounter的值。这种设计保证了外部代码能正常使用该对象,同时又隐藏了对象内部的核心逻辑代码。

循环中常见错误

function fun() {
  for (var i = 1; i < 5; i++) {
    setTimeout(() => {
      console.log(i);
    }, 0);
  }
}
fun();
 /* logs 5 */
 /* logs 5 */
 /* logs 5 */
 /* logs 5 */

在fun函数中,用for循环了设定了4个定时器,0ms后执行,本来我们预期输出的是1、2、3、4,然而结果却是输出了4个5,这是因为for循环中用var声明的循环条件变量其实是fun函数的局部变量,定时器中的回调函数其实也是一个闭包函数,只是它们都共享fun函数的词法环境,等到回调函数执行log输出时,fun函数中变量i已经是5了。

function fun() {
  for (var i = 1; i < 5; i++) {
    (function (i) {
      setTimeout(() => {
        console.log(i);
      }, 0);
    })(i);//用立即执行函数(IIFE)创建闭包引用
  }
}
fun();
 /* logs 1 */
 /* logs 2 */
 /* logs 3 */
 /* logs 4 */

现在我们修改一下fun函数,在每次for循环中都执行一个IIFE,目的就是为了创建闭包引用的独立词法环境(IFEE的词法环境),执行了4次IIFE就会创建4个闭包引用,每个闭包引用的词法环境独立不共享,因此实现了我们预期的输出结果1、2、3、4。当然,在ES6规范中,我们更适合用let声明for循环中的条件变量,利用块级作用域解决。

四、闭包的缺陷

  • 闭包函数引用的变量一直保留在内存中,如果有多个闭包函数,势必造成占用内存过多而导致性能问题。
  • 闭包调用完后不再使用,但没有清空引用闭包函数的变量,会造成内存泄漏。

\

本文为记录自己学习前端知识的个人理解总结,不保证理解正确到位,欢迎评论指出和纠正错误,欢迎一起学习前端。

五、参考引用

MDN闭包

学习Javascript闭包(Closure)阮一峰

js 图解变量环境、词法环境、执行上下文,作用域链查找过程

JS执行上下文(执行环境)详细图解