JavaScript闭包简易指南
本文最后更新于:2021年3月30日 下午
JavaScript闭包简易指南(翻译)
在学习闭包过程之中,发现了medium中一篇比较好理解的外国博客。为方便学习交流,在下初略翻译搬运,并在自己的理解上进行修改和添加了自己的内容.
本文适合初步了解闭包,只从作用域和函数执行过程两个角度简易解释闭包
深入认识闭包还要从设计模式、应用场景、浏览器垃圾回收机制和词法作用域(待更新)
javascript中的闭包是让许多开发者头疼的东西。在下面的文章中,我将清楚地解释何为闭包,并使用简单的范例来阐述要点。
什么是闭包?
闭包是 JavaScript 中的一项功能,其中内部函数可以访问外部(封闭)函数的变量
就是函数和函数变量所处作用域的混合
即形成 范围链(即作用域链,个人认为范围链更容易理解,)。
闭包有三个范围链:
- 它有权访问自己的范围(作用域)——在其大括号
{}
之间定义的变量 - 它有权访问外部函数的变量
- 它可以访问全局变量
小白看到这些行话有可能不太理解
(可自行查阅MDN中的js作用域、变量等知识点)
为什么要用闭包?
简单的闭包例子
看看一个简单闭包的范例:
1 |
|
这里有两个功能:
- 具有变量
b
的外部函数outer
,返回值是inner
- 具有变量
a
的内部函数inner
,在其内部可以访问outer
的变量b
b
的作用范围被限制在函数outer
内,a
则被限制在inner
中
现在调用函数,并赋值存储到变量上。
然后,第二次调用同样的过程
1 |
|
一行一行来康康outer
函数第一次执行过程
变量
b
被创建,它的作用域是outer
之内,此时赋值b=10
这一行开始是函数声明
inner
结束后是return inner
,发现返回的是个函数,此时返回整个inner
函数体
[注意return
并不会执行inner
函数,只有inner
被调用才执行]被返回的内容被存储在变量
x
中, 即x =
1
2
3
4function inner(){
var a = 20;
console.log(a + b);
}函数执行结束,
outer
作用域内的所有变量都被释放,不再存在
这个过程中第四步最为重要,要注意一旦函数结束执行,函数范围内的任何变量都将消失
函数执行时间 === 函数内变量生存周期
既然这样, 当尝试调用X
时,outer
函数的执行已经结束,只在执行时存在变量b
此时已经被释放,那么属于outer
的变量b
在inner
中的console.log(a + b)
就会报错, b应该是undefined
当函数第二次执行,此时赋值给变量Y,重复上述过程
……
第二次执行的重点是,这时的变量都是全新的,同样符合:函数执行时间 === 函数内变量生存周期
现在回到代码,看看X
和Y
,outer
函数执行后都返回同样的函数给变量X
和Y
,那么X
和Y
也理所当然是函数:
1 |
|
执行X
和Y
:
1 |
|
不难看出实际上我们都在执行inner
来一步一步看看执行过程:
- 创建变量
a = 20
- JavaScript 现在尝试执行 。这里是事情变得有趣的地方。JavaScript 知道存在变量
a
, 因为第一步刚刚创建它。
但是,根据变量生命周期,属于outer
的b
不再存在。outer
函数早在我们调用inner
之前就完成了执行,因此函数范围内的任何变量都不再存在 - ……
*上面这个例子就是是要说明需要闭包的原因: 只有函数跑起来的时候变量才存在,那么怎么使用哪些其他本应消失的变量*?
为了解决这个矛盾,JavaScript设计出闭包.
闭 包
上例阐述了:inner
函数可以访问已经结束生命周期的outer
函数中的变量b
用一句话来解释闭包:
一个函数在已封闭的函数执行时,保存并且能够访问范围链内的局部变量(即已封闭的函数的变量),此时产生的就是闭包
用轮子哥的话说(站在闭包的角度):
在我们的示例中,inner
函数保留了执行outer
函数时的值,并继续保留(称为’’封闭’’)
注意有个重点是:
inner只有在使用到了外部的局部变量时,范围链内才出现闭包
现在再理解以下范围链,注意到这条链中确实有变量b
的值,因为b
在outer
函数执行时将闭包的值封闭在闭包内。
所以语句console.log(a + b)
才能够正确执行,得到想要结果
您可以通过向上述示例添加以下代码行来验证这一点:
1 |
|
通过控制台打印出函数执行路径:
可以看到变量 b=10
保留在 inner
中的
Scopes链→
→Closure闭包
→b : 10
现在回到文章开头提出的范围链并和闭包关联起来:
它有权访问自己的范围(作用域)——变量
a
它有权访问外部函数的变量——变量
b
它可以访问全局变量
闭包的实际使用
使用闭包的核心要点,让我们通过添加三行代码展示:
1 |
|
运行这段代码,将在在控制台中看到以下输出:
1 |
|
让我们逐步检查此代码,看看到底发生了什么,并看到闭包的过程!
1 |
|
初始调用函数outer
这是调用的是outer
,执行以下步骤:
- 变量
b=10
,c=100
被创建,作为参考命名为b1,c1
inner
函数被返回并且复制给变量X
此时变量b
被使用到,因此被inner
封闭到它的范围链内,即产生了闭包,这个闭包包含一个变量b : 10
outer
函数完成执行,并且它的所有变量都不再存在。
变量b
也不存在,尽管闭包中保存了b
的引用
1 |
|
这时执行和X()
一样的过程,并且创建全新的环境和变量…..
现在,让我们看看执行以下代码行时会发生什么:
1 |
|
首次调用
- 创建变量
a=20
a
的值来自赋值语句,b
的值来自闭包中保存的b1
,b=10
a++、b++
,自增X()
完成执行,内部变量释放
然而,b1
作为闭包保留,所以继续存在。
第二次调用时
- 再次创建变量
a=20
- 由于上一次执行
b++
后b1
的值增加了,故此时的b=11
a++、b++
,再次自增X()
完成执行,内部变量释放
然而,b1
继续作为闭包保留,所以继续存在。
第三次调用时
同理,此时b=12
首次调用 Y()
时,
- 再次创建变量
a=20
a
的值来自赋值语句,b
的值来自闭包中保存的b1
,b=10
a++、b++
,自增Y()
完成执行,内部变量释放
然而,b1
作为闭包保留,所以继续存在。
前面三次执行X()
和最后一次执行Y()
,区别在于不同的函数构成不同的闭包,是彼此独立的,不会相互影响
##自此就是关于闭包的初步了解,深入了解关注后续博文
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!