JavaScript 闭包

JavaScript 闭包可以让一个内层函数访问到外层函数的作用域,让内层函数可以使用外层函数中定义的变量。

JavaScript 闭包可以让一个内层函数访问到外层函数的作用域,让内层函数可以使用外层函数中定义的变量。

局部变量与全局变量

函数可以使用函数内部定义的所有变量,如下所示:

function myFunction() {
  let a = 4;
  return a * a;
}

函数也可以访问函数外部定义的变量,如下所示:

let a = 4;
function myFunction() {
  return a * a;
}

在第一个示例中,a局部变量。局部变量只能在定义它的函数内部使用。它对其他函数和其他脚本代码隐藏。

在第二个例子中,a 是一个全局变量,在浏览器环境中,全局变量属于 window 对象。页面(和窗口)中的所有脚本都可以使用(和更改)全局变量。

同名的全局变量和局部变量是不同的变量,修改一个并不影响另另一个。

没有使用关键字( varlet、 或 const )声明的变量总是全局的,即使它们是在函数内部创建的:

function myFunction() {
  a = 4;
}

变量的生命周期

全局变量一直存在,直到页面被丢弃,例如当您导航到另一个页面或关闭窗口时。

局部变量的寿命很短。它们在调用函数时创建,并在函数完成时删除。

计数器的例子

假设您想使用一个变量来统计数量,并且您希望该计数器可用于所有函数。

您可以使用全局变量 counter 和一个用来增加计数器的函数:

// 计数器变量
let counter = 0;

// 增加计数器的函数
function add() {
  counter += 1;
}

// 调用 3 次
add();
add();
add();

// counter 的值是 3

上面的方案有一个问题:由于 counter 是全局变量,页面上的任何代码都可以更改 counter 变量,而无需调用 add()。

我们需要 JavaScript 闭包来解决这个问题。

JavaScript 闭包

还记得自执行函数吗?这个函数有什么作用?

const add = (function () {
  let counter = 0;
  return function () {
    counter += 1;
    return counter;
  };
})();

add();
add();
add();

// counter 的值是 3

示例说明:

自执行函数中将计数器 counter 初始化设置为 0 ,并返回一个函数表达式。该函数表达式中实现了对父函数(自执行函数)中的变量 counter 每次增加 1,并返回当前的计数器的数值。

自执行函数定义后立刻执行,并返回的函数表达式。变量 add 被赋值为函数表达式,也就是说 add 是一个函数。

调用 add() 的时候,就会访问到函数表达式父函数中的 counter 变量,从而实现对计数器的修改。

这样 add 就变成了一个函数。“精彩”的部分是它可以访问父作用域中的计数器。

上面的这个方案成为 JavaScript 闭包。闭包让函数和其周围状态(counter)的引用绑定在了一起,也就是闭包让你可以在一个内层函数中访问到其外层函数的作用域。

闭包的一个用处就是可以进行模块化的设计,它可以将部分的变量或者数据隐藏在模块的内部,而只暴露必要的操作,增加了模块的安全性。