前言
闭包(closure)对于大家来说应该已经不陌生了,但是当有人问你时,你真的能讲清楚什么是闭包吗?今天这篇文章通过结合红宝书及黄宝书中对闭包的定义,来讲一下闭包和词法作用域之间的关系及什么是真正意义上的闭包。
关键词: 闭包、词法作用域。
如何查看闭包
首先我们创建一个html文件在浏览器打开,运行一段生成闭包的js代码。
js
function fn() {
var name = 'lgh'
function fn2() {
console.log(name)
}
fn2()
}
fn()
这里以谷歌浏览器为例:F12打开控制台 > 点击Source标签 > Scope > closure。具体操作可看下图示例:

上图在第14行运行时可生成闭包,所以在此处打了个断点,运行到此处就会停下来,在右侧的Scope下的closure属性出可看到在执行fn2时,生成了一个涵盖fn函数作用域的闭包,闭包下包含对外层函数变量name的引用。
闭包的定义
下面是在这两本书中关于闭包的定义。
1.javascript高级程序设计: 闭包指的是那些引用了另一个函数作用域中变量的函数,通常是在嵌套中实现的。
2.你不知道的JavaScript: 当函数可以记住并访问所在词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。
从第二个定义来看 ,其实是不认同上面的例子是闭包的。更准确来说fn2函数对a的引用方法是词法作用域的查找规则,简单来说就是fn2函数是在fn函数的词法作用域中定义并执行了,并不是真正意义上的闭包,而只是闭包的一部分!而真正意义上的闭包应该在自己定义的词法作用域外执行。即以下这段代码才是真正的闭包:
js
function fn() {
var name = 'lgh'
function fn2() {
console.log(name)
}
return fn2;
}
const fn3 = fn()
fn3() // 输出:lgh
fn函数通过return返回fn2,并在fn函数的词法作用域外执行,并且访问到了fn函数内的变量name,这才应该是闭包的效果;而不是引用单纯的词法作用域查找就能访问的变量。
这里也有一个容易理解的误区是只有函数内部返回一个引用了其内部作用域变量的函数才算是闭包。
参考以下例子其实也是闭包:
js
// 函数传递
function fn2(cb) {
cb() // 输出lgh。这也是闭包,在fn的词法作用域外执行了fn3
}
function fn() {
var name = 'lgh'
function fn3() {
console.log(name)
}
fn2(fn3)
}
fn()

js
// 变量赋值
var fn
function fn2() {
var name = 'lgh'
function fn3() {
console.log(name)
}
fn = fn3 // 内部函数赋值给外部变量
}
function fn4() {
fn() // 闭包
}
fn2() // 执行赋值操作
fn4() // 生成闭包

闭包生成条件
闭包是在运行时生成的。闭包 是指一个函数对象,它不仅包含了函数的代码,还记录了函数创建时的环境信息,包括函数内部的变量、参数等。这些环境信息使得闭包可以在函数定义的作用域之外被调用,并且可以访问到其定义时的变量状态。
总结
闭包可以是那些引用了另一个函数作用域中变量的函数。但真正意义上的闭包是在定义它的词法作用域外执行才能发挥它的真正的效果,也是严格意义上的闭包!