日期:2014-05-16 浏览次数:20462 次
在这篇文章中,我们会了解到一些闭包的知识。在了解闭包之前,我们先了解一下 javascript 作用域的概念。
我们知道,在javascript中,是以function作为域的界限,而不是像其他很多语言一样,是以大括号作为边界的。一个定义在函数内部的变量,在函数外部是访问不了的,但是在代码块里面定义的变量,比如一个for循环中定义的变量,在代码块外面是可以被访问到的。
var a = 1; function f(){ var b = 1; return a; } f(); // 1 b; // b is not defined
变量a是一个全局域的,然而b则是在函数f()内部作用域的,所以:
在f()中,a和b都可以被访问到。
在f()外,a可以被访问到,但是b不能。
如果你在f()内部定义一个function n(),那么n()将有访问他自己域和父函数f()域的权限。这就是作用域链,并且链可以任意深。
在javascript中,函数具有词法作用域。意思是,在函数被定义的时候创建他的作用域,而不是在被调用时才创建。例如:
function f1(){ var a = 1; f2(); } function f2(){ return a; } f1(); // a is not defined
在函数f1()内部调用了函数f2()。因为变量a也在f1()内部,所以你的第一反应可能会觉得在f2()中可以访问a,但是并不是这样的。在f2被定义的时候,并没有a这个变量。f2()就像f1()一样,只能访问它自己的作用域和全局作用域。他们并没有共享出他们的本地作用域。
当一个函数被定义时,它会记住它自己的环境、作用域链。这并不意味着他能够知道他作用域里的每一个变量。相反的,我们可以添加、删除、更新函数作用域里面的变量,并且函数可以得到最新的变量的状态。如何在上面的例子中加一个全局变量a,那么f2()就可以访问得到。
var a = 5; f1(); // 5 a = 55; f1(); // 55 delete a; // true f1(); // a is not defined
这种特性给了javascript很大的灵活性。你可以添加和删除变量,并且重新添加他们之后,一样可以继续运行。下面我们试一下把f2()删除以后重新定义他。
delete f2; // true f1(); // f2 is not defined var f2 = function(){ return a * 2; } var a = 5; f1(); // 10
首先,我们用一些图来描述一下闭包。
这是一个全局作用域,就像一个宇宙,包含了所有东西。
它包含了变量a和函数F。
函数有它自己的私有空间来存储它内部的变量和函数,在某些情况下,会像下面的图一样。
如果你在a点,那么你在全局空间中。如果你在函数F中的b点,那么你即在全局空间中,又在函数F的空间中。如果你在函数N中的c点,那么你即在全局空间中,又在函数F的空间中,还在函数N的空间中。但是反过来,b不在函数N的空间里,a不在函数F和函数N的空间里。因此,在a点,不能访问b和c的东西,在b点,不能访问c点的东西。但是在c点可以访问b和a的东西,b点可以访问a点的东西。但是,当函数N从F里面出来到全局空间的时候,这就形成了闭包。
这时,N已经像a一样在全局空间中,并且函数N记住了他自己的状态,N仍然可以访问F的空间中的b。
如何让N像上面这样切断链呢?下面来看一下具体的代码是怎么实现的。
第一种可以把N声明成为一个全局的函数。
function f(){ var b = "b"; n = function(){ return b; } }
在函数中定义了一个变量n,由于没有加var,所以它成为了一个全局函数,但是为了养成好的习惯,建议还是在全局加上一个变量声明。
var n; function f(){ var b = "b"; n = function(){ return b; } } f();
当我们执行了函数f以后,函数n有了他的方法体,由于n在函数f里面,所以可以访问到f的变量b。就是出了函数f的作用域成为一个全局函数,它一样可以访问到f的变量b。
n(); // "b"
第二种可以在F中,把N返回到全局空间中。
仍然使用上面的函数,但是这次的函数和前面的有点区别。
function f(){ var b = "b"; return function(){ return b; } }
?函数包含一个局部变量b,因此,从全局是无法访问到的。
b; b is not defined
但是函数返回了另一个函数,我们可以把他想象成上面的N。这个新的函数可以访问到他的私有空间,f的空间和全局的空间。所以,他可以访问到b。由于在全局区域可以访问函数f,因此,可以把他的返回值重新赋值给一个新的全局变量。这就成为一个可以访问函数f私有空间的一个全局函数。
var n = f(); n(); // "b"
因此,你可以认为闭包就是创建一个能够在其父函数return以后仍然在父作用域中保持关联的一个函数。
当你传入参数到一个函数,你也可以创建一个函数来return他的父函数的参数。
function f(arg) { var n = function(){ return arg; }; arg++; return n; } var m = f(123); m(); // 124
首先来看一下这个程序。
function f() { var a = []; var i; for(i = 0; i < 3; i++) { a[i] = function(){ return i; } } return a; }
?运行此函数以后赋值给数组a
var a = f();
?现在,我们运行数组a中的各个元素。
for(var j=0;j<a.length;j++){ alert(a[j]()); }
可以发现,浏览器弹出3次3。其实,我们在创建了3个指向局部变量i的闭包。闭包并没有记住i的值,而是指向i并返回它当前的值。所以在循环之后,i的值是3。所以就会有我们看到的结果。
要是我们想得到结果是弹出0,1,2的话,应该怎么改呢?其实我们可以利用自执行函数来实现。