前言
JS闭包,对于每一个前端而言都是一个绕不开的概念。本人学习之初,因为闭包这个概念而花费了大量的时间以及精力去理解这个概念。所以在这里,我打算写一篇文章来分享一下本人的学习心得以及我眼中的闭包。
什么是闭包
先来看看百度百科对闭包的定义:
上面提到了局部变量,那什么又是局部变量呢? 在理解局部变量之前,我们需要先知道,一个函数的执行流程是什么样的。
执行上下文
执行上下文(execution context)是JavaScript中最重要的一个概念。执行上下文定义了变量或者函数有权访问的其他数据,决定了它们各自的行为。每个执行上下文都有一个与之关联的变量对象。
全局执行上下文是最外围的一个执行上下文。根据ECMAScript所在的宿主环境的不同,该上下文也不同。在浏览器中,全局执行上下文是windows对象,在全局环境下声明的变量为全局变量,所有的全局变量和函数都是作为window对象的属性和方法创建的。某个执行上下文中的所有代码执行完毕后,该上下文被销毁,保存在其中的所有变量和函数也随之销毁(全局执行上下文知道应用程序退出——例如关闭浏览器或者网页时才会被销毁)
而每个函数也都有自己的执行上下文,当执行流进入一个函数时,函数的环境就会被推入一个环境栈当中。而在函数执行完毕后,栈将其环境弹出,把控制权返回给之前的执行环境。
我们来看个例子:
// 全局环境
var a = "全局环境";
function A() {
var a = "局部环境";
B();
}
function B() {
console.log(a);
}
A();
我们来模拟一下浏览器执行上述代码时的流程。
执行栈中推入全局执行上下文,全局执行上下文中存在全局变量a和函数A与函数B。

- 执行流进入A函数,在
执行栈顶推入函数A的执行上下文,函数A的执行上下文存在局部变量a。

- 执行函数A代码块中的函数B调用指令,
执行栈顶推入函数B的执行上下文,函数B打印变量a。

此时,控制台打印出了全局变量。
讲到这里,我们不禁发出了疑问,根据执行栈的情况来说,理应是函数B逐层向下查找到函数A的执行上下文,并且打印出局部变量才对呀,为什么反而打印出了全局变量呢?
这里就引出了作用域这个概念
作用域
作用域:指的是一个变量的作用范围。
在ES6之前,JS的作用域只有全局作用域以及函数作用域两种,在ES6引入了块级作用域(本文暂且先不讨论块级作用域)。
一个变量如果是在全局环境下定义的,那么这个变量就存在于全局作用域下。以此类推,在函数内部定义的变量,则存在于函数作用域下。

在各自作用域下声明的变量只在各自作用域下有效。
作用域链
而JS的函数在声明时,会创建一个作用域链。它的用途是保证对执行上下文有权访问的所有变量和函数的有序访问。作用域链的前端,始终都是当前执行的代码所在的上下文的变量对象。如果这个上下文是函数,其变量对象为其内部声明的变量以及入参的arguments对象。作用域链中的下一个变量来自包含(外部)上下文,这样一直延续到全局执行上下文。而JS的函数在声明时,采用的是词法作用域,即在声明时就确定好了作用域链。作用域链的定义是静态的!
在上面的例子里,函数A和函数B在定义时,外部执行上下文只有全局执行上下文,所以其作用域链都为:
函数A/B作用域 -> 全局作用域。

所以,上文中的例子,函数B内部通过其作用域链先找其内部的变量对象,发现没有变量a,便向上通过作用域链找到了全局作用域下的全局变量,并最终打印出结果。
那么,怎么才能在全局作用域中获取函数作用域中的值呢?
闭包
说了这么多,闭包它终于来了。闭包是指有权访问另一个函数作用域中的变量的函数。创建闭包的方式,就是在一个函数内部返回一个匿名函数。我们来改造一下上面的那个例子:
var a = "全局环境";
function A() {
var a = "局部环境";
return {
B: function () {
console.log(a);
},
};
}
var obj = A();
obj.B(); // "局部环境"
现在,我们在函数A的内部返回了一个对象,该对象内部有一个函数B。我们在全局环境下调用了这个函数B,结果打印出了局部环境。这就是一个闭包,我们成功的在全局作用域下调用到了函数A作用域中的变量。究竟是怎么回事呢?让我们再来执行一下这段代码:
- 全局执行上下文推入执行栈中,该上下文中存在一个全局变量a,一个函数A和一个对象obj。

- 调用函数A,执行栈中推入函数A执行上下文。该上下文中存在一个局部变量a。

- 执行
obj.B(),将函数B执行上下文推入执行栈中。

- 打印变量a,此时根据
词法作用域,我们画出作用域链。函数A是在全局环境下声明的,所以其作用域链的下一部分指向了全局作用域,而函数B是在函数A中声明的,所以其作用域链的下一部分指向了函数A的函数作用域。

此时,函数B的作用域链指向了函数A的作用域,因此打印出了局部变量。现在,我们通过闭包,可以在全局环境中使用函数作用域下的变量了,是不是感觉很棒。
抽象点来理解闭包:当一个函数内部返回了一个匿名函数,该匿名函数除了自身携带的物品外(内部的变量对象),还背着一个背包(通过作用域链引用的外部函数的变量对象)。在该函数外部就可以通过这个背包来访问这个函数内部的变量了。
结语
闭包其实这个概念并不难以理解,只要了解了执行上下文,作用域,作用域链等概念,就能掌握闭包这个概念。而闭包在JS中的使用是非常广泛的,了解了闭包,相信你的JS功底会更加的扎实。由于本人也是在学习阶段,以上的文章如有不对的地方,欢迎在评论区里指出!
最后,码字不易,如果觉得我写的还不错的,烦请各位大佬点下宝贵的赞,你的支持是我创作的最大动力。QAQ
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!