1.js闭包的理解
1.1 要理解闭包首先理解变量的作用域:变量分为全局变量和局部变量。
全局变量:保存在内存中,在函数定义之外声明的变量,且改变量的值在整个持续访问内都可以修改和改变。
局部变量:在函数内声明定义一个变量,每次执行该函数时都会自动创建和破坏在变量,且它不能被该函数外的任何事物访问。
javascript语言的特殊之处,就是可以直接读取全局变量,另一方面就是在函数外部自然无法读取函数内的局部变量。
var n=999;
function f1(){
alert(n);
}
f1(); // 999
/---------------------------/
function f1(){
var n=999;
}
alert(n); // error
这里有一个地方需要注意,函数内部声明变量的时候使用了var就是局部变量,没有使用var就是全局变量。
Js代码
function f1(){
n=999;
}
f1();
alert(n); // 999
2.如何从外部获取内部的局部变量:
通常情况下是办不到的,这就需要我们在函数内部在定义一个函数,然后返回。
function f1(){
n=999;
function f2(){
alert(n); // 999
}
}
在上面的函数中,f1的所有属性对于f2都是可见的,反而f2的内部属性对于f1就是不可见的。这就是javascript所特有的“链式作用域结构”,子对象会一层一层地向上级寻找所有父类的变量,所以父类的所有变量对子对象都是可见的。
既然f2可以读取f1中的局部变量,那么只要把f2作为返回值,我们不就可以在f1外部读取它的内部变量了吗!
Js代码
function f1(){
n=999;
function f2(){
alert(n);
}
return f2;
}
var result=f1();
result(); // 999
3.闭包的概念:
上述代码函数f2就是闭包。
我们先来理解一下js解释器的作用域链:简单的说作用域链就是函数在定义的时候创建的,用于寻找使用到的变量的值的一个索引,而它的内部规则是:把函数自身的本地变量放在最前面,把自身的父级函数中的变量放在其次,把在高一级的函数变量放在更后面,以此类推直至全局对象为止,当函数中需要查询一个变量的值时候,js解释器会去从作用域链中去查找,从最前面的本地变量去查找,如果没有找到对应的变量,则到下一级的链上找,一旦找到了变量,则不再继续,如果最后也没有找到需要的变量,则解释器返回undefined.
现在我们来理解一下js的内存回收机制:一般来说,一个函数在执行开始的时候,会给其中定义的变量划分内存空间保存,以备后面的语句所用,等到执行完毕返回了,这些变量就被认为是无用的了,对应的内存空间也就被回收了,下次在执行函数的时候,所有的变量又回到最初的状态,重新赋值使用,但是如果这个函数内部又嵌套了另外一个函数,而这个函数是有可能在外部被调用到的,并且这个内部函数又使用了外部函数的某些变量的话,这种内存回收机制就会出现问题,如果在外部函数返回后,有直接调用了内部函数,那么内部函数就无法读取到它所需要的外部函数中的值了,所以JS解释器在遇到函数定义的时候,会自动把函数和它可能使用的变量(包括本地变量和父级和祖先级函数的变量(自由变量))一起保存起来,也就是构建一个闭包,这些变量将不会被js内存回收机制所回收,只有当内部的函数不可能被调用以后(例如,删除了或者没有了指针)才会销毁这个闭包,而没有任何一个闭包引用的变量才会被下一次内存回收启动时所回收。
也就是说有了闭包,嵌套的函数结构才可以运作,这也是符合我们的预期的。
闭包的难懂的一些特性:
看下面代码:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>js闭包理解</title>
<script type="text/javascript">
var result=[];
var foo=function(){
var i=0;
for(;i<3;i++){
result[i]=function(){
alert(i);
}
}
}
foo();
result[0]();
result[1]();
result[2]();
//输出的结果都是3
//这是为什么呢?
//
</script>
</head>
<body>
</body>
</html>
这段代码程序员希望foo函数中的变量i被内部循环的函数使用,并且能分别获得他们的索引,而实际上只能获得该变量之后保留的值,也就是说,闭包中所记录的自由变量,只是对这个变量的一个引用,而并非变量的值,当这个变量被改变了,闭包里获取到的变量值,也会被改变。
解决方法之一就是让内部函数在循环创建的时候立即执行,并且捕捉当前的索引值,然后记录在一个本地变量里,然后利用返回函数的方法,重写内部函数,让下一次调用的时候,返回本地变量的值,改进后的代码:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>js闭包理解2</title>
<script type="text/javascript">
var result=[];
var foo=function(){
var i=0;
for(;i<3;i=i+1){
result[i]=(function(j){
return function(){
alert(j);
}
})(i);
}
}
foo();
result[0]();
result[1]();
result[2]();
</script>
</head>
<body>
</body>
</html>
闭包获值的例子:获取函数内部的局部变量给外部引用
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>js闭包获值的例子</title>
<script type="text/javascript">
var outFun=function(){
var a=0;
var innerFun=function(){
a++;
alert(a);
return a;
}
return innerFun;
}
var obj=outFun();
var test1=obj();//obj()第一次执行内部函数innerFun,返回a=1给test1,
alert(test1+"a111111");//获取内部函数返回的值a=1
obj();//执行内部函数并返回外部函数的值,第二次执行内部函数a=2
obj();//第三次执行内部函数a=3
var obj2=outFun();
obj2();//重新第一次执行内部函数a=1
obj2();//第一次执行内部函数a=2
</script>
</head>
<body>
</body>
</html>