前言
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介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!