我觉得闭包是一种更自然的代码形式,可以让你不拘泥于简单的块、函数作用域而穿透作用域。
举2个例子,一个用于隐藏一些变量,另一个用于异步调用:
1、隐藏变量:
这个是最原始的Prototype框架里bind函数(绑定函数作用域)的最简化版本。在这里,我们可以看到调用bind后对obj对象的隐藏(这段代码可以理解为一种转移作用域的代理模式吧)。
2、异步调用。
假如我们有一段代码
需要改成异步延时的,只需要改成
其实经验上讲,绝大多数的同步的地方改成异步都都可以用上述方法,只要将需要异步的部分(通常是从某一行开始到函数末尾)包含在闭包中,就可以进行异步处理了。
其实还有挺多其他应用,总之,如果心里想着“什么是闭包”,“我在用闭包”这种事的话,反而往往用不好闭包。只要深刻的理解js这种特性,这种作用域机制,将闭包使用在无形之中,才算是真正会使用闭包了吧。
谢谢,发现问题所在了,我取的时候也是3,是New一个对象,然后,apply的,直接调用确实不是,不过,我有点不太明白什么时候要用闭包,什么时候不要用,虽然我的程序中闭包用得也比较的多,那是不得已的时候才用的 ,能给点经验吗?
往大里说,闭包是一种跟面向对象技术同样层面的“把数据与操作进行绑定”的技术。只不过面向对象是基于数据(对象实体)来封装操作(对象方法),而闭包是基于操作(函数)来封装数据(自由变量)
往小里说,使用闭包的最常见的场景是用来模拟“柯里化”。所谓柯里化,就是假如你有一个带有多个参数的函数,你可以通过指定其中某些参数的值,来产生参数较少的新函数。例如:
柯里化有两个用处,
1是你可以为柯里化之后的方法命名,更有效地表达业务逻辑。
2是为回调函数提供接口上缺失但在绑定时可以确定的运行期信息。就是当回调函数接口无法提供满足业务需求的足够上下文信息,而这些信息在运行期注册回调函数时可以确定。这时我们在编程时可以设计出具有额外接口参数的回调函数,在注册回调函数时运用Currying创建出携带有这些信息的符合接口规范的新函数。
例如:
其中3,6,7是根据业务需求硬编码的,没有规律(也就是说在你绑定onclick时才知道到底是用3,6,还是7)。为了把回调函数抽象统一起来,如果不用闭包,你就必须把这个绑定期的信息缓存起来,以便在回调时可以使用:
这种代码一多就搞得很乱,而且缓存信息的安全性也无法保证(很可能在回调之前就被其他人误操作修改了)。如果用闭包你可以:
还有就是你可以通过闭包为某个函数实例保存一些内部的全局状态,这与面向对象类似,但是写起来比较方便简洁,避免了多余的传参或全局变量。例如:
代码是随手打的,没有测试,只是表达一下意思。
举2个例子,一个用于隐藏一些变量,另一个用于异步调用:
1、隐藏变量:
- Function.prototype.bind = function(obj) {
- var _this = this;
- return function() {
- _this.apply(obj,arguments);
- };
- }
这个是最原始的Prototype框架里bind函数(绑定函数作用域)的最简化版本。在这里,我们可以看到调用bind后对obj对象的隐藏(这段代码可以理解为一种转移作用域的代理模式吧)。
2、异步调用。
假如我们有一段代码
- function test() {
- var a = 1,b=2,c=3;
- alert(a+b+c);
- }
需要改成异步延时的,只需要改成
- function test() {
- var a = 1,b=2,c=3;
- setTimeout(function() {
- alert(a+b+c);
- },1000);
- }
其实经验上讲,绝大多数的同步的地方改成异步都都可以用上述方法,只要将需要异步的部分(通常是从某一行开始到函数末尾)包含在闭包中,就可以进行异步处理了。
其实还有挺多其他应用,总之,如果心里想着“什么是闭包”,“我在用闭包”这种事的话,反而往往用不好闭包。只要深刻的理解js这种特性,这种作用域机制,将闭包使用在无形之中,才算是真正会使用闭包了吧。
9 楼 kidneyball 2011-03-11
suiye007 写道
谢谢,发现问题所在了,我取的时候也是3,是New一个对象,然后,apply的,直接调用确实不是,不过,我有点不太明白什么时候要用闭包,什么时候不要用,虽然我的程序中闭包用得也比较的多,那是不得已的时候才用的 ,能给点经验吗?
往大里说,闭包是一种跟面向对象技术同样层面的“把数据与操作进行绑定”的技术。只不过面向对象是基于数据(对象实体)来封装操作(对象方法),而闭包是基于操作(函数)来封装数据(自由变量)
往小里说,使用闭包的最常见的场景是用来模拟“柯里化”。所谓柯里化,就是假如你有一个带有多个参数的函数,你可以通过指定其中某些参数的值,来产生参数较少的新函数。例如:
- function f(x,n) { //求幂
- var result = 1;
- for (var i = 0; i <n; i++) {result *= x;}
- return result;
- }
- function currying(n) {
- return function(x) {return f(x,n);};
- }
- var square = currying(2);
- var cube = currying(3);
- alert(squre(2)); //4
- alert(cube(2)); //8
柯里化有两个用处,
1是你可以为柯里化之后的方法命名,更有效地表达业务逻辑。
2是为回调函数提供接口上缺失但在绑定时可以确定的运行期信息。就是当回调函数接口无法提供满足业务需求的足够上下文信息,而这些信息在运行期注册回调函数时可以确定。这时我们在编程时可以设计出具有额外接口参数的回调函数,在注册回调函数时运用Currying创建出携带有这些信息的符合接口规范的新函数。
例如:
- btn1.onclick=function() {....;n=n*3;....};
- btn2.onclick=function() {....;n=n*6;....}; //...部分与btn1一致
- btn3.onclick=function() {....;n=n*7;....}; //...部分与btn1一致
其中3,6,7是根据业务需求硬编码的,没有规律(也就是说在你绑定onclick时才知道到底是用3,6,还是7)。为了把回调函数抽象统一起来,如果不用闭包,你就必须把这个绑定期的信息缓存起来,以便在回调时可以使用:
- btn1.x = 3;
- btn2.x = 6;
- btn2.x = 7;
- function click() {...,n=n*this.x,...};
- btn1.onclick=click;
- btn2.onclick=click;
- btn3.onclick=click;
这种代码一多就搞得很乱,而且缓存信息的安全性也无法保证(很可能在回调之前就被其他人误操作修改了)。如果用闭包你可以:
- function makeClick(x) {return function(x) {...;n=n*x;...}};
- btn1.onclick=makeClick(3);
- btn2.onclick=makeClick(6);
- btn3.onclick=makeClick(7);
还有就是你可以通过闭包为某个函数实例保存一些内部的全局状态,这与面向对象类似,但是写起来比较方便简洁,避免了多余的传参或全局变量。例如:
- function makeCounter() {
- var a = 0;
- return function() {return ++a;);
- }
- var counter1 = makeCounter();
- var counter2 = makeCounter();
- counter1(); counter1();
- alert(counter1()); //3
- counter2();
- alert(counter2()); //2
代码是随手打的,没有测试,只是表达一下意思。