首先先引用《JavaScript权威指南》里面的一句话来开始我的博客:函数的执行依赖于变量作用域,这个作用域是在函数定义时决定的,而不是函数调用时决定的。
因此,就出现了如下的几串代码:
var a="outside"; function area(){ var a="inside"; function b(){ return a; } return b(); } area();
结果为:inside
这样的结果并不太出乎意料,因为在执行area()的时候,返回值就已经是b()了,也就是函数b已经是调用之后才被返回的,当然返回的是局部变量a。
那如果我返回的值不是执行函数b后的结果,而是函数b呢?
var a="outside"; function area(){ var a="inside"; function b(){ return a; } return b; } area()();
结果还是inside
js函数的执行用到了作用域链,而作用域链是在函数定义的时候创建的,在上面两个例子中,函数b()是定义在局部作用域里面的,也就是说,它的返回值a早就注定是局部变量的a了,无论外面的area()函数的返回值是否为执行过的函数b()的结果。其实这个就是闭包,为什么闭包能够让局部变量的值始终保持在内存中?《JavaScript权威指南》里面有这样一段话:每次调用函数,都会为之创建一个新的对象来保存局部变量,然后把该对象添加至作用域链中(每次调用就创建一个新的,调用多少次,创建多少个,执行结果互不影响)。当函数返回时,本来应该是直接从作用域链中将这个对象删除,但是闭包的出现让这一切变得不简单。当返回的是一个嵌套函数的时候,就会有一个外部的引用指向这个嵌套的函数,可以理解为外部对它进行调用,或者赋值给某个变量,在js垃圾回收机制中,一旦某个变量不再被引用,那么这个变量将会被回收。由此可见之前绑定在作用域链上的对象由于闭包的关系不会被当做垃圾回收,这也就是闭包能够让局部变量的值始终保持在内存中的原因。
接下来我们来看一下几段有关于闭包和作用域的代码,这几段代码都采用自权威指南。
function add(){ var num=0; return { count:function(){return num++;}, reset:function(){num=0;} }; } var a=add(),b=add(); //创建两个计时器 a.count(); //0 第一行 b.count(); //0 第二行 a.reset(); //重置 第三行 a.count(); //0 第四行 b.count(); //1 第五行
这段代码的结果正好印证了每次调用就会创建不同的对象,然后保存在作用域链上。第一行和第二行是两个计时器对计时函数的调用,很明显他们互不影响,第三行a计时器进行重置,当然对b计时器无效了,互不影响的嘛!
下面这两段代码是我们经常碰到的笔试题
function a(v){ return function(){return v;}; } var funs=[]; for(var i=0;i<10;i++){ funs[i]=a(i); } console.log(funs[5]()); //5 function a(){ var funs=[]; for(var i=0;i<10;i++){ funs[i]=function(){ return i; } } return funs; } var funs=a(); console.log(funs[5]()); //10
第一段代码执行结果为5,第二段为10。原因可以根据前面的篇幅来解释。
第一段代码只有一个闭包,但是有10个外部调用函数,也就是10个对象保存在作用域链上,执行结果互不干扰,所以当调用funs[5]() 的时候,结果肯定也是5。
第二段代码有十个闭包,共享同一个外部函数的局部变量,外部调用函数只有一个,当9次循环执行完之后,i还被++了,所以结果是10,所以外部函数调用到内嵌函数的时候,结果为10。