前言
在JavaScript中,闭包是一个非常重要的概念,对于编写高质量的JavaScript代码和理解某些设计模式非常关键。很多人认为闭包很难,不过我相信你看完了我的文章会有收获,或许对闭包有一个新的认识
在讲闭包之前,大家可以先看看我之前的文章,有关于调用栈的讲解,配合本篇文章食用效果更佳哦~
文章快捷入口:调用栈
首先我们先来看一道题
js
var arr = []
for (var i = 0; i < 10; i++){
arr[i] = function () {
console.log(i);
}
}
for (var j = 0; j < arr.length; j++){
arr[j]()
}
我们先来思考一下,最后会打印什么呢?让我们输出一下
可以看到,输出了10个10。这里为什么会输出10个10呢?当函数arr[j]调用时,要输出i的值,所以在arr[j]的执行上下文中寻找i,但是并没有找到i,接下来再它的外层作用域中寻找i,在这里也就是全局作用域,找到了i,在全局区中i的值通过for循环已经变成了10,所以当console.log(i)时,打印的是10个10。
那如果我们想要打印 0,1,2,3,4,5,6,7,8,9,我们将这段代码改一下,应该怎么改呢?这个问题我们先留在这,在我们学习了闭包之后,就可以轻松解决。
闭包
在JS中,根据词法作用域的规则,内部函数总是可以访问其外部函数中声明的变量的, 当内部函数被返回到外部函数之外时,即使外部函数执行结束了, 但是内部函数引用了外部函数的变量,那么这些变量依旧会被保存在内层中, 我们把这些变量的集合称为闭包
这个概念是不是有些难以理解呢?不用担心,我们接下来拿一道题来解释一下
js
function foo() {
var myName = '菌菌'
let test1 = 1
let test2 = 2
var innerBar = {
getName: function() {
console.log(test1);
return myName
},
setName: function (newName) {
myName = newName
}
}
return innerBar
}
var bar = foo()
bar.setName('来颗奇趣蛋')
console.log(bar.getName());
我们先来看一下输出结果
我们画调用栈的图来分析一下这道题
首先,全局执行上下文入栈
调用foo()函数,foo()执行上下文入栈
foo()函数执行完成,出栈
接下来,我们执行bar.setName('来颗奇趣蛋')
接下来我们发现,有一个赋值语句,myName = newName ,可是,由于foo()已经出栈,bar.setName的执行上下文和全局区的执行上下文都没有myName 这个变量,并且当bar.setName('来颗奇趣蛋') 执行完成出栈时,调用bar.getName()函数,它的执行上下文入栈,打印输出console.log(test1) 时,我们发现也找不到test01这个变量,那么最后为什么会输出1 和 '来颗奇趣蛋'呢?
其实,虽然foo()的执行上下文出栈了,但还是留下了一个'小书包',这里面存放了myName和test01,,如图
这里小伙伴们可能就要问了,那么多变量都不留,为什么就留这两个呢?这里我们来解释一下,因为setName()和getName()定义在了foo()里面,setName()和getName()是可以访问foo()里面的变量的,但可是setName()和getName()并不是在foo()里面调用的,而是在全局区调用的。
foo()创建了这两个函数,但并没有调用这两个函数,而是通过return返回一个对象。通俗一点来讲,当这两个函数在外部调用时,foo()并不能控制这两个函数,而这两个函数调用时,使用了foo()函数里的test1变量
和myName变量
,foo()函数不知道这两个函数什么时候会被调用,而当这两个函数调用时,它们会向foo()函数讨要test1
和myName
,所以foo()虽然出栈了,但是会把两个函数调用时需要使用的变量留下,当两个函数调用时,就可以直接来取。而图中黄色的框所包裹的变量,我们就称之为闭包。
看完了这个例子,我们是不是可以理解了开头那段概念。当内部函数(setName和getName被返回到外部函数(foo)之外时,这里也就是全局区,虽然foo()执行完毕了,但是之后两个函数执行时,引用了foo()内的变量,这些变量依旧会保存在内层之中,而这些被保存的变量的集合 ,我们就称之为闭包(closure)。
接下来我们再看一个例子来掌握一下:
js
function a() {
function b() {
var bbb = 234
console.log(aaa); //输出123
}
var aaa = 123
return b
}
var c = a()
c()
直接上图理解:
内部函数b()被返回到了外部函数a()之外,这里也是全局区,内部函数调用时,引用了a()中变量aaa,尽管a()已经出栈,但这些变量依旧会被保存下来,所以输出123.
我们也可以使用闭包的方法,让开头那个例子打印出0,1,2,3,4,5,6,7,8,9
js
var arr = []
for (var i = 0; i < 10; i++){
(function a() {
var j = i
arr[i] = function () {
console.log(j);
}
}) ()
}
for (var j = 0; j < arr.length; j++){
arr[j]()
}
总结
在JS中,根据词法作用域的规则,内部函数总是可以访问其外部函数中声明的变量的, 当内部函数被返回到外部函数之外时,即使外部函数执行结束了, 但是内部函数引用了外部函数的变量,那么这些变量依旧会被保存在内层中, 我们把这些变量的集合称为闭包
今天的内容就到这啦,如果你觉得小编写的还不错的话,或者对你有所启发,请给小编一个辛苦的赞吧