武汉web培训
达内武汉中心

15827352908

热门课程

javascript闭包学习总结

  • 时间:2016-06-01 10:54
  • 发布:醒着的猫
  • 来源:web前端网

这几天闲着没事,开始继续学习js的高级程序设计。开始研究闭包了,感觉这个东西很抽象,于是又是查资料又是翻红本书最后总算弄明白了闭包是怎么回事。在这里,以我的理解跟大家分享一下,同时也希望有高手可以指点一下,看看我的理解是不是有问题。

要说到闭包,就要先谈一下js的作用域链,先来看下面的一个例子

var a=0;
function f1(){
a=5;
}
f1();
alert(a);//  5

在程序开始运行时,js要为程序建立一个执行环境,执行环境定义了变量或者函数有权访问的其他数据,决定了各自的行为。按照顺序原则,全局是最先建立执行环境的,我们叫他全局执行环境。他是最外围的一个执行环境,每个执行环境都有一个与之关联的变量对象(简称vo),环境中定义的所有变量和函数都保存在那里。
相对于上面的例子则是
Global.vo={
a
f1
}
一开始所有的变量的初始值都为undefined,此时程序执行后,解析器寻找到var关键字,于是就会对变量a分配内存空间,此时我们又对a进行了赋值,于是就有了a=0;
(简单说一下,var标识符的意思是告诉电脑给变量分配内存空间,实际上这段代码应该是这样
var a //为a分配内存,当在这一步时,a就被分配到了全局执行环境中记录。
a=0  //到这一步时,解析器开始对变量对象进行扫描,然后匹配对应的变量并且把值投到分配好的内存中去。


接下来按照顺序 开始执行函数f1,这时js开始为f1创建执行环境,同时还会创建一个相应的作用域链用于记录对执行环境中有权访问的所有变量和函数的有序访问。这个链条的最前端永远是当前执行的代码所在环境的变量对象,其中我们把函数的变量对象的活动对象(简称ao)作为变量对象。这个对象一开始的时候建立了一个保存函数中的参数(arguments)。于是则有以下内容
f1.ao={
arguments:undefined
}
于此同时,js还创建了这个函数的一个特殊内部对象用于保存作用域链,我们把这个变量叫做[[scope]](以下用[s]简称)。在作用域链中,第一位永远是当前执行函数,其余的外部函数永远都是从第二位开始……一级一级的分配,一直到全局执行环境。于是在这里就有这么一种关系

f1={
[[scope]].scope chain[0]=f1.ao
[[scope]].scope chain[1]=Globel.vo
}
在[[scope]]中所保存的scope chain我们可以把它看成是一个数组,于是我们就可以说作用域链的本质实际上就是指向变量对象的指针列表(在oo语句中也可以说是对变量对象的引用),当解析器遇到一个变量名的时候,解析器会按照作用域链中排好的顺序在每个变量对象中找合适的内容。找到后便赋值给他。

那么现在我们来看这个例子
var a=0;
function f(){
var a=5;
a=6
alert(a);
}
f();        //6
alert(a);// 0

按照我们刚才说的 在屏幕上返回的两个值都应该是6 为什么其中一个返回来的却是0呢?

原因在一开始我已经讲过了,var标识符的意思是提醒程序别忘了对变量分配空间。于是当解析器发现有两个var的时候,就分配了两个内存保存数据。并且这两个的名字都是a
在全局中发生的是
var a=undefined(独立内存)
在f1中同样发生的是
var a=undefined(另一个独立内存)
然后函数执行,a的重新赋值肯定满足就近原则,解析器搜索作用域链的时候,先搜到哪个就把值先给谁。

当函数执行完毕,函数就会随着作用域链等一并清空。那么我们可以想全局执行环境是从浏览器(web)打开那一刻就运行起来的,所以全局执行环境只有当浏览器(web)页面关闭的时候被回收。

那么我们就能明白 为什么外部函数不能读取内部函数的变量 来看下面的例子

function f(){
var b=3;
}
alert(b); //error
f();
alert(b); //error
当我们第一次输出b的值的时候返回错误 这个很好理解,因为函数没有执行,自然也就没有为b分配内存,但是当我们执行完f后 b仍旧返回错误,也就是说程序仍旧没有为b分配内存,原因刚才已经说了,函数执行完后 就会被立即清空,所以包括变量b在内,也就一并的从内存中清空了。

那么我们现在想要显示b的值,要么就让另外的一个变量保存这个值,要么就让这个值永远在内存里,于是我们就改为
function f(){
 var b=3;
return b;
}
然后我们在创建一个变量c来保存返回来的值这时程序变为

function f(){
var b=3;
return b;
}
var c=f();
alert(c); //3
这时我们就看到b的值了。但是这个就是闭包了么,我们来看下面的例子。
function f(){
var b=3;
++b;
return b;
}
var c;
for (i=0;i<3;i++){
c=f();
alert(c);
}

上面的例子我们只是做了一个小小的改动,如果变量b没有被回收,那么也就说明当我们再次执行f的时候,b的值会+1,那么最终我们能够显出出的值为 4,5,6可事实我们来看c却返回了4,4,4。也就是说,变量b并没有被永久的留在内存中。但是我们有时需要维持一个变量在内存中,假设我们希望每次c中所获得的值是上升的,那么闭包就会十分重要而且非常有效了

创建闭包只要在f的内部创建一个函数即可(这里并不是说匿名函数是闭包),下面 为了得到我们的期望,我们修改上面的代码
function f(){
var b=3;
function inner(){
++b;
return b;
}
}
var c=f();;
for (i=0;i<3;i++){
alert(inner())
alert(c()); //4,5,6
}
这里我们解释一下,大家要知道,在js中,当一个变量或者函数不被再引用时,js的垃圾内存回收机制就会回收它,在刚才的例子中我们建立了一个内部函数inner,然后再inner中 我们让变量b进行计算并返回,然后 现在外部函数f的返回值返回的是inner这个函数。于是这个函数就被我们创建的变量c所引用,这就意味这函数inner不会被销毁(因为c是定义在全局环境中,全局环境要在关闭这个页面后才能被销毁)由于inner函数的作用域链中保存着对外部函数f的活动对象的引用,因此f也不会被销毁,自然b也不会被销毁,于是b就被牢牢的保存在了内存里,请注意由于c此时是对函数inner的引用,因此c也是一个函数,所以我们要调用函数的话必须使用c()进行。
在ECMAscript中,闭包被解释为:闭包是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。这句话读起来十分的拗口,也不好理解,红本书中阐释的闭包概念显得更为简洁:闭包是指有权访问另一个函数作用域中的变量的函数。所以说实际上在js中每个函数都可以称之为闭包。

从以上我们可以看出,由于函数执行完后没有被回收处理,因此闭包是很耗费内存的,所以闭包虽好,也要适量。

马上预约七天免费试听课

姓名:

电话:

上一篇:html/css的一些注意点
下一篇:武汉web培训:高性能CSS

web前端学习容易犯的错误

web1702“诱人”活动

抽象类和抽象方法

jquery的ajax和getJson跨域获取json数据

选择城市和中心
贵州省

广西省

海南省

有位老师想和您聊一聊