当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行的。
function foo() {
var a = 1; // a 是一个被 foo 创建的局部变量
function bar() { // bar 是一个内部函数,是一个闭包
console.log(a); // 使用了父函数中声明的变量
}
return bar();
}
foo(); // 1
foo() 函数中声明了一个内部变量 a , 在函数外部是无法访问的,bar() 函数是 foo() 函数内部的函数,此时 foo 内部的所有局部变量,对 bar 都是可见的,反过来就不行,bar 内部的局部变量,对 foo 就是不可见的。这就是javaScript特有的”作用域链“。
function foo() {
var a = 1; // a 是一个被 foo 创建的局部变量
function bar() { // bar 是一个内部函数,是一个闭包
console.log(a); // 使用了父函数中声明的变量
}
return bar;
}
const myFoo = foo();
myFoo();
这段代码和上面的代码运行结果完全一致,其中不同的地方就是在于内部函数 bar 在执行前,从外部函数返回。foo() 执行后,将其返回值(也就是内部的 bar 函数)赋值给变量 myFoo 并调用 myFoo(), 实际上只是通过不同的标识符引用调用了内部的函数 bar()。
foo() 函数执行后,正常情况下 foo() 的整个内部作用域被销毁,占用的内存被回收。但是现在的 foo的内部作用域 bar() 还在使用,所以不会对其进行回收。bar() 依然持有对该作用域的引用,这个引用就叫做闭包。这个函数在定义的词法作用域以外的地方被调用,闭包使得函数可以继续访问定义时的词法作用域。
例子:
定时器,事件监听器,Ajax请求,跨窗口通信,Web Workers或者其他异步或同步任务中,只要使用回调函数,实际上就是闭包。
注意:
闭包容易导致内存泄漏。
闭包会携带包含它的函数作用域,因此会比其他函数占用更多的内存。过度使用闭包会导致内存占用过多,所以要谨慎使用闭包。
总结:
- 闭包是指有权访问另一个函数作用域中变量的函数。
- 闭包通常用来创建内部变量,使得这些变量不能被外部随意修改,同时又可以通过指定的接口来操作。