前端之路-了解什么是闭包理解闭包就像LOL点加了一个技能,对我们帮助很大,理解闭包对于深入掌握 JavaScript 语
在开始介绍闭包之前,先给大家看一段代码,现在看不明白不要紧,在看完我对闭包的介绍后,你肯定就能看懂他 var arr = [];
for (var i = 0; i < 10; i++) {
arr[i] = function () {
console.log(i);
}
}
arr.forEach(function (item) {
item()
})
在了解什么是闭包之前我们需要先了解这两个概念 作用域链 和 词法作用域
🐟作用域链
- 函数在执行前预编译,会创建执行上下文对象
- 变量环境中有一个内置的outer属性用于指明的外层作用域是谁
- outer的指向是根据词法作用域来的
js引擎查找函数变量先会在函数中查找,找不到就会根据outer的指向去到外层作用域去查找,层层往上,这种查找的关系链就称为作用域链
🐟 词法作用域
一个域所处的环境,是由函数声明的位置来决定的
我们来看一个例子
var myname = 'tom'
let test1 = 100
if (1) {
let myname = 'jerry'
console.log(test, myname);
}
}
function foo() {
var myname = '彭于晏'
let test = 2
{
let test = 3
bar()
}
}
var myname = '晟哥'
let test = 1;
foo()

因为foo函数和bar函数都定义在全局变量,所以他们的outer都指向全局变量.foo()
函数执行,因为bar()
在foo()
内,所以bar()
也会执行,因为bar()
内没有test
这个变量,他就会去outer
指向的外层作用域去查找,他声明在了全局中,所以他会去全局中去找test
,最后找到了test=1
,所以最后输出的是1,jerry
通过这个例子,我们现在对这两个概念应该有了初步的了解,outer并不是从上往下依次指向的,而是应该根据词法作用域的的位置来决定,词法作用域就是函数声明的地方(记住并不是调用的地方)。
接下来我们通过这两个概念去了解闭包到底是个啥?
大家先看下面这段代码,分析一下他们会输出什么?
function foo() {
var name = '大仙'
function bar() {
console.log(count, age);
}
var count = 1
var age = 18
return bar
}
var age = 20
const baz = foo()
baz()
首先我们需要去写一个调用栈去分析出他们的全局上下文

全局中定义了age
,foo()=fun
,还有此法环境的baz=func
,然后去执行foo()
,写出foo()
中声明的变量 他的outer
指向全局,因为他声明在全局,foo()
执行完会立即销毁。然后是全局调用baz()
的执行,baz
其实就是bar()
,只是不同的名字,函数体一样。baz()
指向的是foo()
,但是foo()
已经被销毁了
所以我们是不是无法输出count,age
了,应该输出的是报错 ,但是结果却和我们分析的大相径庭
为啥不仅没有报错,还输出了在已经销毁了的foo()
中所声明的变量,首先我们需要知道一个函数执行完毕后,他的执行上下文一定会被销毁,我们可以想象成每当一个函数执行完了,就会有一个清洁工大爷去清理调用栈的执行上下文,以免占地方.可是当foo
执行完时,大爷过去清洁它的时候,大爷问他你是不是已经执行完毕了,我要把你清理出调用栈了,但是这个时候foo却回答我也不知道我是否已经执行完了,因为foo
说他还有个逆子,虽然是foo
生的,但是确却家出走,不在foo
体内调用,所以foo
并不知道bar
是否已经被调用了,而且儿子(bar)
在走的时候还说了他会回来的,要用一个背包去留着他房间的床(count)和桌子(age)
,出于人道主义,清洁工大爷把其他的都销毁了,但是留下了床和桌子。

红线是儿子(bar)的查找规则,先是此法环境再是调用环境然后是outer指向,指向了上文的背包,我们把这个背包就称为闭包,即函数销毁之后留下的小背包.
下面还有一个例子帮助你理解闭包
function foo() {
var a = 1
var b = 2
function bar() {
console.log(a);
}
bar()
}
foo()
在这段代码中,bar
仍然声明在foo
中但是不同的是他没有在foo
中返回即return
,所以foo()
要执行完要等bar()
执行完才行所以在bar
指向的时候,foo()
并不会被销毁,bar
可以去访问foo
声明的变量 a
对比这两个例子我们可以了解闭包是怎么形成的,就是当一个函数定义在一个函数体内,又不在他的体内调用,在外部调用,这就是第一个例子.这是闭包的形成条件但是什么是闭包呢?
🐟闭包 :
根据js词法作用域的规则,内部函数总是能访问外部函数的变量,当通过调用一个外部函数返回的一个内部函数后,即使外部函数已经执行完了,但是内部函数引用了外部函数中的变量也依旧需要保存在内存中,我们把这些变量的集合就是闭包.
在这里有个坑,外部函数返回并不是说只有return才是,还有其他的方式,请看下述代码
function foo() {
var a = 1
var b = 2
function bar() {
console.log(a);
}
window.fn = bar
}
foo()
window.fn()
这样也会形成闭包,并不是只有return这个方法,只要你能拿的出去.
🐟闭包的作用
还是老规矩我们先看一段代码
function add() {
var count = 0
count++
return count
}
console.log(add());
console.log(add());
console.log(add());
你觉得会输出什么?是 1 2 3
吗,并不是,答案是1 1 1
.这是为什么呢,因为每次调用bar
都会把count
初始化为0,我们再改良一下
将var count=0提出去
var count = 0
function add(){
count++
return count
}
console.log(add());
console.log(add());
console.log(add());
这样会输出 1 2 3
,但是这样声明了一个全局变量,非常不利于开发,会让代码后期的维护很难.因为开发中不只是你一个人在开发,是大家在一起开发,全局变量很容易影响别人的代码.
再改良一下
function add() {
var count = 0
function foo() {
count++
return count
}
return foo
}
var bar = add()
console.log(bar());
console.log(bar());
console.log(bar());
在每次调用bar时,bar都会去找到bar下一级(foo)背包(闭包)
中的count
,这样每次foo
中的count
都会++
这样输出的也是 1 2 3
这个时候我们就能初步明白闭包的作用
1.实现共有变量(企业的模块开发)
2.做缓存
3.封装模块,防止全局变量污染
虽然闭包好用。但是不能过度使用,他也有缺点,不能闭着眼睛乱用闭包.他是一把双刃剑
调用栈的可用空间会被闭包占用,可用栈空间变小 所以内存泄漏
现在我们在对开头的代码进行分析
var arr = [];
for (var i = 0; i < 10; i++) {
arr[i] = function () {
console.log(i);
}
}
arr.forEach(function (item) {
item()
})
运行代码 输出的全是10
我们怎么改这段代码可以让他输出 0~9呢,我们可以使用闭包去实现
var arr = [];
for (var i = 0; i < 10; i++) {
function foo(){
var j =i
arr[j] = function () {
console.log(j);
}
}
}
arr.forEach(function (item) {
item()
})
foo
每执行一次都会创建一次执行上下文,循环10次,会创建10个foo的执行上下文,他们在调用栈都不能被销毁,调用栈留下了10个闭包
我们再让这段代码优雅一点,使用立即执行函数
var arr = [];
for (var i = 0; i < 10; i++) {
(function foo() {
var j = i
arr[j] = function () {
console.log(j);
}
})()
}
立即执行函数:(function() { /* 函数体 */ })();
在 JavaScript 中,立即执行函数是一种非常有用的技术,可以帮助你更好地组织和管理代码。
还有一种更优雅的写法
var arr = [];
for (var i = 0; i < 10; i++) {
! function foo(j) {
// var j = i
arr[j] = function () {
console.log(j);
}
}(i)
}
i作为实参传进去,j作为形参接受数值.
Ending
看到这里相信你已经对闭包也算初步了解了,如有看不懂地方可以在评论区提问,大家一起相互交流👀
转载自:https://juejin.cn/post/7422154695743389708