到底该怎么去理解闭包?
今天看到了一段关于闭包的代码:
代码片段A:
!function(){
var num=1;
var exp={};
function add(num){
return num++;
}
exp.getAddNum=function(){
return add(num);
}
window.a=exp;
}()
console.log(a.getAddNum()); // 1
console.log(a.getAddNum()); // 1
代码片段B:
!function(){
var num=1;
var exp={};
function add(){
return num++;
}
exp.getAddNum=function(){
return add();
}
window.a=exp;
}()
console.log(a.getAddNum()); // 1
console.log(a.getAddNum()); // 2
谁能解释下这2段代码的区别吗?考验大家基本功的时候到啦~~~~
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(14)
第一个里面是你传递进去的,他会使用当前作用域接收到的这个形参的值,它并没有去改变外层num的值,因此你每次用它来传递,值都是1。
而第二个的'add'方法中并没有num变量,他会通过作用域链找到外层的num,那么你这样调用时每次都是操作的外层变量的值,而这个值在你
return
之后是会累加的。关于上下文和作用域链你可以看看这篇文章
图解Javascript上下文与作用域
与闭包密切相关的两个概念是作用域链和词法作用域。简单解释下:
作用域链:JavaScript不存在大括号级的作用域,但有函数作用域,在函数内声明的变量在函数外不可见,而在代码块内声明的变量在代码块外是可见的。同时,一个作用域可以访问其内部变量,也可以访问其父级作用域的变量,比如函数内可以访问全局变量。
词法作用域:在声明一个函数的时候就会创建该函数的作用域环境,当其被调用的时候,它可以访问其内部作用域的变量,以及其父级作用域的变量。即便变量是在其之后声明的,一样可以访问。因为记录的是作用域范围,而不是作用域内的具体变量名。
举个例子
全局函数
a
的声明是在全局作用域,b
的赋值虽然在其后,但仍然可以访问。而局部函数d
的声明则是在函数c的局部作用域里面,因此在全局作用域不能访问。这时候闭包的作用就出来了!
来看一个最简单的代码
变量
f
虽然是在闭包的局部作用域里,但由于e
引用到了返回的匿名函数,而这个匿名函数处在变量f
所在的作用域,可以访问之,因此在全局作用域里仍然可以得到f
的值。所以,闭包能够有效地减少对全局变量的依赖,并且保护局部变量(不能直接访问局部变量),同时延续局部变量寿命(在上面例子中,变量
e
保留了对返回的匿名函数的引用,因而其作用域没有在自调函数运行结束后被回收,变量f
也就延续了寿命)。好哒,最后我们来看一看,闭包如何面向对象进行设计。
闭包可以模仿其他面向对象语言的private和public。来举个PHP的例子吧:
那如果是在JavaScript里面我们怎么写呢?
第一,可以直接创建对象
第二,使用构造器
以上是使用JavaScript来设计对象,但
name
作为对象的属性,在JavaScript里面是完全暴露的,我们并不想这样。那怎么办?这样,
name
变量就完成了对private的模拟,同时返回了具有Getter和Setter作用的对象,可以操作name
变量。这么跟你讲,一个养猪场里有一个火腿肠加工厂,猪送进去进行加工最后运出火腿肠,这个加工过程不会对猪场的其他猪产生影响,这个加工厂就叫做 闭! 包!
片段A里
add(num)
里操作的是自己的参数……每次调用的时候都是add(1)
,当然返回也是1.片段B里
add()
里操作的才是闭包里那个变量num
。你把A里的函数改成
就好理解了
这都是不合理的变量命名造成的混淆……不在于对闭包的理解
红皮书上有一章讲函数的参数是值传递去看下会有帮助
第一个例子中的add方法将num作为形参传递进方法,在该方法的内部实际是该变量的一个副本,因此自增时是不会改变外部num的值的,所以每次调用的结果都为1。
第二个例子中没有将num传递进add方法,那么每次调用add时会从上级的作用域中去寻找该变量。因此add方法内num自增时也就会对外部的num值修改了。
第一个例子中的add方法有混淆视听的作用,add中的参数就是一个形参,还定义成num,迷惑人。
我只想说,这个例子太啰嗦了,要是想对比,下面这样就足够了
上面的代码对应例子A
上面的代码对应例子B
运行代码还是
在第一段代码中,首先
var num = 1
为值类型,在调用add(num)时中的实参num复制了var num = 1
的值,然后将值传递给定义函数时的命名参数num,在函数调用运行时,这个命名参数num是作为此函数的局部变量运行。在第二段代码中在调用
add()
时,此时的var num = 1
是作为嵌套函数add外部作用域的变量被使用,形成了闭包,因此结果被累加。可以看看我的这篇文章,执行环境与作用域链以及函数执行
这是理解闭包的前提
关于函数参数传递可以看看这篇文章js中值的访问与参数传递的问题
变量定义时形成对应的ao对象(活动对象),例子2 函数add形成一个对象ao = { num:1,function... } 这里的num值来自于var定义的值,但是和上文sum没有任何关系 是新建的ao对象中的元素,所以他的++不影响上文,而例子1中参数来自于上文var 值则一直取得是var定义的num 来形成ao,即每一次新形成的ao里的num刚加了1 就被新形成的ao中的num给替换了。
问题的关键是:函数调用传参时,基本类型是按值传递的,也就是说 num 会被复制一份,实际上 return num++ 的 num 并不是 var num = 1 的 num,而是它的一个副本。
刚好我有整理过闭包这一块,可以看看,http://www.cnblogs.com/skylor/p/4721816.html
闭包最直观的理解,函数里面调用函数。改变执行环境,这个执行环境只有里面的函数才能访问到,外面的想访问他必须通过这个外部函数作为桥梁。就是变量没被销毁而是存下来,更直观点变量作用域是由声明的时候决定多,与执行过程无关。