闭包是 JavaScript 中一个非常重要的,而且是其中最具特色的概念之一。让我们一起了解一下什么是闭包以及它为什么如此重要。
什么是闭包?
根据 MDN 对于闭包的解释:
闭包 (closure)是一个函数以及其捆绑的周边环境状态(lexical environment,词法环境)的引用的组合。换而言之,闭包让开发者可以从内部函数访问外部函数的作用域。在 JavaScript 中,闭包会随着函数的创建而被同时创建。
闭包的实质是一个函数可以访问其外部作用域的变量,即使这个函数在其外部作用域已经退出。这种特性使得闭包能够在函数调用之间保留状态,实现跨函数的数据传递。
javascript
function outer() {
var outerVar = '外部作用域的变量'
function inner() {
console.log(outerVar) // inner 函数可以访问 outer 函数中的变量
}
return inner
}
var closureFunc = outer()
closureFunc() // 输出 "外部作用域的变量"
在上面的例子中,inner
函数形成了一个闭包,它可以访问 outer
函数中的 outerVar
变量,即使 outer
函数已经执行完毕并且 outer
函数的执行上下文已经出栈。这是因为 inner
函数继续保持对 outer
函数作用域的引用,这就是闭包的本质。
闭包的工作原理
作用域链(Scope Chain)
要理解闭包的工作原理,需要了解 JavaScript 的作用域链机制。
JavaScript 中的作用域链是一个从函数内部到外部作用域的链式结构。当一个函数内部引用外部变量时,JavaScript 引擎会从内部函数开始,沿着作用域链向上查找,直到找到该变量的定义为止。作用域链的查找顺序是:首先查找函数内部的局部变量,然后是其外部祖先函数的局部变量,最后是全局作用域。
变量提升(Variable Hoisting)
JavaScript 中,变量提升是指变量在声明后,其作用域会向上提升至最近的函数外部作用域。这意味着即使在一个函数内部声明了一个外部变量,JavaScript 引擎也会在函数外部为其创建一个全局变量。因此,当内部函数引用了外部变量时,实际上是在引用全局变量。
闭包的实现
闭包的实现依赖于作用域链和变量提升。当一个内部函数引用了外部变量时,JavaScript 引擎会在内部函数中创建一个对该外部变量的引用。当内部函数执行完毕,其作用域消失,但内部函数对外部变量的引用仍然存在。此时,如果外部函数继续执行,其作用域链中的外部变量会被提升,从而使得内部函数引用的外部变量得以保留。
闭包的实际应用
封装数据
一个常见的用途是通过闭包创建私有变量,以实现数据的封装和保护。这有助于防止其他代码意外地修改或访问数据。
javascript
function createCounter() {
var count = 0
return {
increment: function () {
count++
},
decrement: function () {
count--
},
getValue: function () {
return count
},
}
}
var counter = createCounter()
counter.increment()
counter.increment()
console.log(counter.getValue()) // 输出 2
在这个示例中,count 变量是私有的,只能通过返回的对象中的方法进行访问和修改,从而确保数据的封装性和隐私性。
延迟执行
闭包还可以用于创建延迟执行的函数,例如在事件处理程序中。
javascript
function createDelayedFunction(message, delay) {
return function () {
setTimeout(function () {
console.log(message)
}, delay)
}
}
var delayedFunc = createDelayedFunction('延迟消息', 2000)
delayedFunc() // 2 秒后输出 "延迟消息"
在这里,createDelayedFunction 函数返回一个闭包,它可以延迟执行一段代码,以便在指定的时间后输出消息。