理解 JavaScript 中的闭包
闭包是 JavaScript 的难点之一。大多数教程只告诉你,闭包就是一个函数中的另一个函数,但这只是闭包的表象。本篇文章就带你透过表象,看看闭包的本质。
写 JavaScript 而不知道什么是闭包,就如同写 Java 而不知道什么是类 --- JSON之父 Douglas Crockford
首先看看下面这一段代码:
// 这段代码理论上来讲,是一个闭包
// 但是并不纯正
var x = 0;
function addNumber() {
return 1 + x;
}
上面这段代码并不是纯正的闭包。x 定义在全局作用域中,所以并不能保证它不被修改。说到这里要提一下,JavaScript 是一个词法作用域的语言。这意味着在函数外定义的变量可以在函数内使用,而反过来则不行。你不能在函数外部使用一个在函数内定义的变量。
下面的代码是一个闭包,x 定义在函数外部。而且在函数执行完之后 addNumber 仍然可以访问到 x 的值。
function closedFunction(x) {
// 当我们把代码用函数包裹起来时
// 就创建了一个函数作用域
// 传递给函数的变量都是独立的
function addNumber() {
return 3 + x;
}
return addNumber;
}
console.dir(closedFunction(3));
在浏览器中打印出来的值证明了 x 是一个闭包
那么问题来了,如果消耗函数外部的变量就是闭包,那为什么下面这段代码不是闭包呢?
function closedFunction(x) {
var numberItem = x + 3;
return numberItem;
}
console.dir(closedFunction)
当我们调用函数时,会创建一个函数作用域并为它分配内存。这个过程会一直持续到函数执行结束,内存被释放为止。在函数执行结束后,作用域中的值也将永远消失。
变量的作用域不在函数外部,所以不构成闭包
但是如果用一个函数包裹另一个函数,它将创建另一个作用域 --- 它旨在告诉 JavaScript,在这个函数执行完之后不要立即销毁它。
下面这段代码中的变量 counter 是一个闭包,因为它在被调用的函数之外( Increment) 。
变量counter 是上述代码片段中的闭包
在某种程度上,闭包只是具有保留数据的函数。创建闭包其实是在告诉JavaScript记住函数中事物的状态 --- 只有被使用的变量才会被视为 闭包
因为闭包是有状态函数,它们在被调用之后会记住其私有变量数据。由于变量是私有的,外部函数无法通过显示调用来访问这些私有变量。这样可以使整个函数自成一体,并且可以保护其变量免受不必要的更改。
词法作用域
因此,JavaScript 中的闭包是一种在无需显示创建类的情况下,就能将代码模块化且自包含的方式方法。使用闭包可以实现代码的可复制性,而且不用担心污染全局作用域。
闭包不仅仅是将一个函数放在另一个函数中的行为,它是一种用于创建可防止外部更改私有变量的技术,这些变量与程序的其他部分隔离,并且具有持久性。