什么是闭包?
函数嵌套函数,内部函数能访问外部函数的变量
优点 隔离作⽤域,不造成全局污染
缺点 闭包⻓期驻留内存,会导致内存泄露
一句话:闭包是指有权访问另一个函数作用域中变量的函数
闭包的演示
在深入探讨闭包的工作原理之前,让我们看一下闭包的一些实际示例。
示例1
function person() { let name = 'Peter'; return function displayName() { console.log(name); }; } let peter = person(); peter(); // prints 'Peter'
在此代码中,我们正在调用 person
函数,该函数返回内部函数 displayName
,并保存了name变量。当我们调用 peter
函数(实际上是在引用 displayName
函数)时,名称 Peter
被打印到控制台上。
但是我们没有在 displayName
函数中声明 name
变量,因此即使外层函数返回后,该函数也可以以某种方式访问其外部函数的变量 name
。因此,displayName
函数实际上产生了一个闭包。
示例2
function getCounter() { let counter = 0; return function() { return counter++; } } let count = getCounter(); console.log(count()); // 0 console.log(count()); // 1 console.log(count()); // 2
同样,我们将 getCounter
函数返回的匿名内部函数赋值给 count
变量。count
函数现在就成为了一个闭包,即使 getCounter()
执行完毕,它依然可以访问 getCounter
函数内部的 counter
变量。
但是请注意,每次 count
执行的时候 counter
的值并不会重置为 0
。
这是因为,每次调用 count()
,将会为该函数创建一个新的作用域,但是这里只给 getCounter
创建了一个作用域,又因为 counter
变量是定义在 getCounter
函数作用域内部的,所以它将在每次 count
调用的时候自增而不是被重置。
闭包是如何工作的
到目前为止,我们已经讨论了什么是闭包及其实际示例。现在,让我们了解闭包在 JavaScript
中如何真正起作用。
要真正了解闭包在 JavaScript 中是如何工作的,我们必须了解 JavaScript 中最重要的概念,执行上下文。
执行上下文
执行上下文是一个抽象的环境,JavaScript 代码在其中执行。当全局代码执行时,它将在全局执行上下文中执行,而函数代码将在函数执行上下文中执行。
每次只能有一个执行上下文处于运行状态,因为 JavaScript 是单线程语言,它由执行栈或者调用栈来管理。
执行堆栈是具有 LIFO(后进先出)结构的堆栈,只能从堆栈顶部添加或删除项目。
当前正在运行的执行上下文将始终位于堆栈的顶部,并且当当前正在运行的函数完成时,其执行上下文将从堆栈中弹出,并且指针将指向堆栈中位于其下方的执行上下文。
详细的闭包示例
现在我们了解了执行上下文,让我们回到闭包。
示例1
看下面的代码:
function person() { let name = 'Peter'; return function displayName() { console.log(name); }; } let peter = person(); peter(); // prints 'Peter'
person
函数执行时,JavaScript 引擎会为该函数创建新的执行上下文和词法环境。该函数执行完毕之后,它会返回 displayName
函数并把它赋值给 peter
变量。
因此其词法环境如下所示:
由于 displayName
函数中没有变量,因此其环境记录将为空。在执行此函数期间,JavaScript 引擎将尝试在此函数的词法环境中查找变量 name
。
由于 displayName
函数的词法环境中没有变量,因此它将查看外部词法环境,即 person
函数仍在内存中的词法环境。JavaScript 引擎找到该变量并将 name
打印到控制台。