什么是闭包❓
闭包(closure)是指有权访问其他函数作用域中变量的函数—— JavaScript 高级程序设计
首先,闭包是一个函数,其次,它能够访问其他函数作用域中的变量。
来看下面的例子
var data = []
for (var i = 0; i < 3; i++) {
data[i] = function () {
console.log(i)
}
}
// 期望输出 0 1 2,实际是 3 3 3
data[0]()
data[1]()
data[2]()
上面的代码之所以没有产生预期的效果,是因为 i
是一个全局变量,当程序执行到 data[0]()
data[1]()
data[2]()
时他们引用的都是同一个变量 i
,此时循环已经结束,i
最终的值为3。
为了使程序产生预期的效果,我们可以这么做
var data = []
for (var i = 0; i < 3; i++) {
(function (i) { // ❗注意,这个 i 是形参,是这个立即执行函数的内部变量,实际上可以任意命名
data[i] = function () { // 变量的查找同样遵循就近原则,里面的 i 指的是形参 i
console.log(i)
}
}(i)) // ❗注意,这个 i 是实参,是 for 循环中的 i
}
data[0]() // 0
data[1]() // 1
data[2]() // 2
上面的代码中,我们将每次循环的 i
以参数形式传递给立即执行函数,然后在立即执行函数内部定义 data[i]
函数。
按照之前对闭包的定义,data[0]
data[1]
data[2]
都是闭包,他们能够访问立即执行函数内部的形参 i
。在其他一些程序设计语言中,上面的代码看起来是不正常的,因为函数执行完毕后,这个函数作用域内的变量就会被释放,这些变量应该只在函数运行时有效才对。但是在 JavaScript 中,函数会形成闭包,当有其他函数引用了这个函数作用域内的变量时,在这个函数执行完毕后,被引用的变量不会被释放。
在这个例子中,要达到预期的效果还有一种更优雅的方式?
var data = []
for (let i = 0; i < 3; i++) { // 仅仅将 var 替换为了 let
data[i] = function () {
console.log(i)
}
}
data[0]() // 0
data[1]() // 1
data[2]() // 2
详解
MDN 对闭包的定义是
闭包是函数和声明该函数的词法环境的组合。这个环境包含了这个闭包创建时所能访问的所有局部变量。
在 ES6 之前,JavaScript 并没有块级作用域的概念,只有 全局作用域 和 函数作用域。任何不是定义在函数内部的变量,都是一个全局变量,可以在全局内访问。
ES6 提出了块级作用域的概念,if
的 {}
内,for
循环内等都是一个块级作用域,但是❗注意,函数内部不是块级作用域,它依然叫函数作用域。因为块级作用域对 var
关键字声明的变量不起作用,而函数作用域对 var
变量有约束作用,配合块级作用域使用的是 ES6 提出的新的关键字 let
和 const
。
let
关键字声明的变量只在当前作用域(既包含块级作用域也包含函数作用域)内有效,所以在 for
循环内定义的 let
变量就有了和函数内部的变量类似的特性。
在浏览器的调试工具中可以证实上面的说法