闭包:我标记了一处“地点”
前面我们已经介绍了
js
中作用域以及预编译的概念,正是因为它们俩规则的冲突碰撞下,就萌生出了闭包的概念,闭包在js
中也是很重要的。

闭包
今天我们就来聊聊什么是闭包?闭包(Closure),它指的是一个函数能够访问并记住其自身作用域以外的变量的能力。具体来说,当一个内部函数引用了外部函数的变量时,就形成了一个闭包。这个内部函数可以访问外部函数的局部变量,即使外部函数已经执行完毕,只要内部函数还被引用,这些外部变量就不会被销毁,从而形成了一个“封闭”的环境。
从概念上看,比较难理解闭包;首先让我们来看以下一段代码:
function bar() {
console.log(a);
}
function foo() {
var a = 100
bar()
}
var a = 200
foo();
在我们之前学过的知识中,我们会默认它应该是打印100
;因为在调用栈中,函数bar
应该会去foo
的词法环境中找到定义的变量a=100
,然后再将其打印,可结果真是如此吗?

结果打印出来是全局中声明的变量200
;在这里就要明白作用域链的概念了。
作用域链
每个函数在执行前会进行预编译,会创建执行上下文对象,在每一个执行上下文对象中还有一个内置属性outer
,内定的outer
属性用于指明该函数的外层作用域是谁,而outer
属性的指向怎么判断呢?规则是,outer
的指向是根据词法作用域来定的。
在js引擎在查找变量时会先在函数中查找,找不到就会根据outer的指向去到外层作用域中查找,层层往上,这种查找的关系链就称为作用域链。

根据上图,就可以得出上面代码的执行结果应该是全局作用域中的200
。
词法作用域
关于outer
属性的指向问题,我们还得知道js中词法作用域的概念;在词法作用域中,变量的可访问性取决于它们在源代码中的位置,即它们在代码中的“词法”或“语法”位置,而不是它们在运行时的位置;一个域所处的环境,是由函数声明的位置来决定的;
也就是说,变量的可见性和生命周期在编译阶段就可以确定,而不依赖于程序运行时的动态行为。
闭包
同样地,让我们再来看下面一段代码;
function foo(){
var name = 'kun'
function bar(){
console.log(count,age);
}
var count = 1;
var age = 18;
return bar
}
var age = 20
const baz = foo();
baz()
这是一个嵌套函数,并且内部函数被返回到外面使用;正因为内部函数被返回到外面使用,这时在调用栈中,函数foo
的调用就已经完成了,按理来说foo
的执行上下文对象应该出栈销毁;但是又根据作用域的规则:内部作用域可以访问外部作用域;这时在外面调用的bar
函数,根据outer
的指向应该要到函数foo
里面查找属性age
,但是foo
的执行上下文对象不是被销毁了吗?这时候,就要引入闭包了。
因为作用域的规则,所以函数foo
为它的内部函数bar
留下了一个小背包,里面包含着函数foo
里面的变量,这个“背包”就被称为闭包。
闭包 面试题
在了解完闭包后,我们可以来看一个面试常考题;
var arr = []
for (var i = 0; i < 10; i++){
arr[i] = function() {
console.log(i);
}
}
arr.forEach(function(item){
item()
})
面试官:怎么把上面的代码改成正常预期输出?
这里考察的就是对闭包的使用:
var arr = []
for(var i = 0; i <10; i++) {
function foo(){
var j = i
arr[j] = function() {
console.log(j);
}
}
foo();
}
arr.forEach(function(item){
item()
})
上面就是把变量i
逐个储存到函数foo
的闭包之中,然后调用内部函数时就能够访问到i
的值。
小结
根据js
词法作用域的规则,内部函数总是能访问外部函数中的变量,当通过调用一个外部函数返回的一个内部函数后,即使外部函数执行已经结束了,但是内部函数引用了外部函数中的变量也依旧需要被保存在内存中,我们把这些变量的集合叫做闭包。
转载自:https://juejin.cn/post/7386958406136004658