初次接识这个概念是在一本讲 C# 的书上。当时读了之后一知半解,后来也没怎么用它就闲置没管它。
不过最近做前端里面有用到 Javascript 对象里一系列的东西,不得不又去温习了一下闭包。
《Javascript 语言精粹》一书中说“闭包是 Javascript 一系列高级技巧的钥匙”。因为有了闭包,才有了 Javascript 提供可伸缩、可管理作用域的可能。
我只是谈谈闭包,它是 Javascript 中的一种机制;闭包也是大多数编程语言都提供的一种能力,为编程语言的表达能力做了很大的提升。
简而言之,闭包就是使用方法体把正当前处于自己作用域的变量框住,让它得以继续。
考虑这样一段代码:
1 2 3 4 5 |
function doSomething(){ var a='Hello World'; setTimeout(function(){ alert(a); },2000); } |
这是一段很简洁的代码,可它却已经使用了闭包。
如果你调用 doSomething 方法,程序会在 2 秒之后弹出提示。不过再细看一下,它还是有一点悬机的。弹出提示的时候,由 setTimeout 调用的那个匿名方法内部没有声明变量 a,它怎么知道 a 是什么呢?按理说,2秒之后, doSomething 方法早已经运行完毕了啊,可结果是确实可以正确地执行:a 的值被保留了下来。
这就是闭包:在这个函数被声明的时候,当时的作用域可以访问到 a 就可以了。
闭包被广泛地运用在对象框架和注册异步回调编程中。
在对象框架中,可以用它来模拟保存局部变量,也就是只有内部方法可以访问到的变量——关于这部分,请参考阮一峰的学习 Javascript 闭包一文
在异步编程中,可以用它来保存一个在回调时可能已经消逝的对象,从而让这个对象继续存在。
最常见的异步编程有在 web 中有异步请求,在虚拟执行环境(JVM 或 .NET)中有多线程处理等,都可以用于闭包。
在 Javascript 中,一个最常见的 Ajax 请求的过程如下:
1 2 3 4 5 6 7 |
// 构造 XmlHttpRequest 对象 xhr // 初始化 xhr,定义其各种属性,如 url、请求方式等 // 下面要给它绑定一个回调,即当 ajax 完成时要执行的任务。 xhr.onreadystatechange = function(){ doSomething(xhr); //注意这里 } //发送异步请求 |
注意上面标有“注意这里”的那行,在那里使用了闭包,把 xhr 对象本身闭包进了一个匿名函数(即回调函数本身)里,然后在 ajax 回来时,目标位置就可以继续使用 xhr 对象而不用担心此时当前调用位置的 xhr 变量已消失了。我们的回调目标函数可能是这样的:
1 2 3 |
function doSomething(xhrObj){ alert('Ajax 回调完成,回调的结果是:\n' + xhrObj.responseText); } |
我们知道,C# 的多线程模型中,要创建一个新的线程需要使用这样的语句:
System.Theading.Thread t = new System.Theading.Thread(new System.Theading.ThreadStart(taskMethod));
从上述语句可以看出,实际上是使用了一个方法taskMethod(不论是静态或是实例的)作为新建的线程需要执行的任务的入口;但问题就来了,如果我想在执行的时候同时传入一些参数那应该怎么做呢?那看了上面的文字,你也许已经想出来了:使用闭包。taskMethod 是我们能左右的,同样,它所在的作用域也是我们所能左右的,所以就能使用闭包把我们要使用的变量闭包进多线程执行过程中了。示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class Task { public string taskArg1; public string taskArg2; public void doSomething() { //多线程要执行的任务 Console.WriteLine("参数1的值是:{0}",taskArg1); Console.WriteLine("参数2的值是:{0}",taskArg2); } } Task ta = new Task(); ta.taskArg1 = '变量1的值'; ta.taskArg2 = '变量2的值'; System.Theading.Thread t = new System.Theading.Thread(new System.Theading.ThreadStart(ta.doSomething)); |
嗯,就是这样的简单,一切问题迎刃而解!
希望有用。