这里有新鲜出炉的 Javascript 教程,程序狗速度看过来!
Javascript 是一种由 Netscape 的 LiveScript 发展而来的原型化继承的基于对象的动态类型的区分大小写的客户端脚本语言,主要目的是为了解决服务器端语言,比如 Perl,遗留的速度问题,为客户提供更流畅的浏览效果。
这篇文章主要介绍了 JavaScript 中的闭包 (Closure) 详细介绍, 函数调用对象与变量的作用域链、什么是闭包等内容, 并给出了实例, 需要的朋友可以参考下
闭包是 JavaScript 中一个重要的特性,其最大的作用在于保存函数运行过程中的信息。在 JavaScript 中,闭包的诸多特性源自函数调用过程中的作用域链上。
函数调用对象与变量的作用域链
对于 JavaScript 中的每一次函数调用,JavaScript 都会创建一个局部对象以储存在该函数中定义的局部变量;如果在该函数内部还有一个嵌套定义的函数 (nested function),那么 JavaScript 会在已经定义的局部对象之上再定义一个嵌套局部对象。对于一个函数,其内部有多少层的嵌套函数定义,也就有多少层的嵌套局部对象。该局部对象称为 "函数调用对象"(ECMAScript 3 中的"call object",ECMAScript 5 中改名为"declarative environment record",但个人认为还是 ECMAScript 3 中的名称更容易理解一些)。以下面的函数调用为例:
- function f(x){
- var a = 10;
- return a*x;
- }
- console.log(f(6));//60
在这个简单的例子中,当调用 f()函数时,JavaScript 会创建一个 f()函数的调用对象 (姑且称之为 f_invokeObj),在 f_invokeObj 对象内部有两个属性:a 和 x;运行 f() 时,a 值为 10 而 x 值为 6,因此最后的返回结果为 60。图示如下:
当存在函数嵌套时,JavaScript 将创建多个函数调用对象:
- function f(x){
- var a = 10;
- return a*g(x);
- function g(b){
- return b*b;
- }
- }
- console.log(f(6));//360
在这个例子中,当调用 f()函数时,JavaScript 会创建一个 f()函数的调用对象 (f_invokeObj),其内部有两个属性 a 和 x,a 值为 10 而 x 值为 6;运行 f() 时,JavaScript 会对 f()函数中的 g()函数进行解析定义,并创建 g()的调用对象(g_invokeObj),其内部有一个属性 b,b 值与传入参数 x 相同为 6,因此最后的返回结果为 360。图示如下:
可以看到,函数调用对象形成了一条链。当内嵌函数 g() 运行,需要获取变量值的时候,会从最近的函数调用对象中开始进行搜索,如果无法搜索到,则沿函数调用对象链在更远的调用对象中进行搜寻,此即所谓的" 变量的作用域链 "。如果两个函数调用对象中出现相同的变量,则函数会取离自己最近的那个调用对象中的变量值:
- function f(x){
- var a = 10;
- return a*g(x);
- function g(b){
- var a = 1;
- return b*b*a;
- }
- }
- console.log(f(6));//360, not 3600
在上面的例子中,g()函数的调用对象 (g_invokeObj) 和 f()函数的调用对象 (f_invokeObj) 中均存在变量 a 且 a 的值不同,当运行 g()函数时,在 g()函数内部所使用的 a 值为 1,而在 g()函数外部所使用的 a 值则为 10。图示此时的函数调用对象链如下:
什么是闭包?
在 JavaScript 中所有的函数 (function) 都是对象,而定义函数时都会产生相应的函数调用对象链,一次函数定义对应一个函数调用对象链。只要函数对象存在,相应的函数调用对象就存在;一旦某函数不再被使用,相应的函数调用对象就会被垃圾回收掉;而这种函数对象和函数调用对象链之间的一一组合,就称之为 "闭包"。在上面 f()函数和 g()函数的例子中,就存在两个闭包:f()函数对象和 f_invokeObj 对象组成了一个闭包,而 g()函数对象和 g_invokeObj-f_invokeObj 对象链一起组成了第二个闭包。当 g()函数执行完毕后,由于 g()函数不再被使用,因此 g()闭包被垃圾回收了;之后,当 f()函数执行完毕后,由于同样的原因,f()闭包也被垃圾回收了。
从闭包的定义可以得出结论:所有的 JavaScript 函数在定义后都是闭包 – 因为所有的函数都是对象,所有的函数在执行后也都有其对应的调用对象链。
不过,令闭包真正发挥作用的是嵌套函数的情况。由于内嵌函数是在外部函数运行的时候才开始定义的,因此内嵌函数的闭包中所保存的变量值 (尤其是外部函数的局部变量值) 是这次运行过程中的值。只要内嵌函数对象依然存在,那么其闭包就依然存在(闭包中的变量值不会发生任何改变),从而也就实现了保存函数运行过程的信息这个目的。考虑以下这个例子:
- var a = "outside";
- function f() {
- var a = "inside";
- function g() {
- return a;
- }
- return g;
- }
- var result = f();
- console.log(result()); //inside
在这个例子中,当运行 f() 函数时,g() 函数被定义,同时创建了 g() 函数的闭包,g() 闭包包含了 g_invokeObj-f_invokeObj 对象链,因此保存了 f() 函数执行过程中的变量 a 的值。当执行 console.log() 语句时,由于 g 函数对象仍然存在,因此 g() 闭包也依然存在;当运行这个仍然存在的 g 函数对象时,JavaScript 会使用依然存在的 g() 闭包并从中获取变量 a 的值 ("inside")。
来源: http://www.phperz.com/article/17/0412/273620.html