闭包和构造函数

好难。。这个适合深夜一个人阅读!!!

大家都知道,得到对象有两种方式,第一是构造函数,第二是闭包,两种方式各有优点,而且用的情景也是不一样的,但是从“外观上”看,有个比较醒目的区别就是:闭包创建的对象不必用new。我们先来看看两者的具体区别:

构造函数:

优点:

1.理解起来相对较简单

2.可以实现继承

3.原型方法用的同一块内存

缺点:

1.如果没有做特殊处理,必须要用new运算符

2.无法实现私有属性,所有属性都会暴露在对象中

闭包:

优点:

1.无需使用new运算符

2.可以利用作用域干很多事情

3.可以将不想被外部看到的属性封装在闭包内部

缺点:

1.比较不好理解

2.不好实现继承

3.无法运用apply和call,因为是利用作用域实现的。

光说没意思,先来个例子先:

构造函数方式:

[javascript] view plain copy
  1. function Person(name,age){  
  2.   this.name=name;  
  3.   this.age=age;  
  4. }  
  5. Person.prototype.sayName=function(){  
  6.   alert(this.name);  
  7. }  

闭包方式

[javascript] view plain copy
  1. function createPerson(name,age){  
  2.     return {  
  3.       sayName:function(){  
  4.         alert(name);  
  5.       }  
  6.       getAge:function(){  
  7.         return age;  
  8.       }  
  9.     }  
  10. }  

在jquery内部中,deferred对象,jqXHR都是采用的闭包的方式,而主构造函数、Event事件对象采用的是构造函数的方式。

我们先说说deferrd对象,说之前先看看是怎么用的,最值得一提的就是jq的pipe方法,当然后面变成then方法。由于我看的源码是1.7.1版本的,所以就叫pipe方法吧,这个方法可以说是deferred的精髓。

[javascript] view plain copy
  1. var defer1=$.Deferred();  
  2. defer1  
  3.  .pipe(function(arg){  
  4.     var defer=$.Deferred();  
  5.     setTimeout(function(){  
  6.         console.log(arg++);  
  7.         defer.resolve(arg);  
  8.     },1000);  
  9.     return defer;  
  10.  })  
  11.  .pipe(function(arg){  
  12.     var defer=$.Deferred();  
  13.     setTimeout(function(){  
  14.         console.log(arg++);  
  15.         defer.resolve(arg);  
  16.     },1000);  
  17.     return defer;  
  18.  })  
  19.  .pipe(function(arg){  
  20.     console.log(arg);  
  21.     return arg;  
  22.  })  
  23.  .pipe(function(arg){  
  24.     console.log(arg);  
  25.  });  
  26.   
  27. defer1.resolve(1);  
结果:


打开浏览器后:1s 后   打印1   再1s后  打印2  3  3

对于单线程的js来说,学会异步编程是非常重要的,而这里也使用职责链的模式让结构更加清晰,对于deferred整体是怎样实现的我就不一一赘述,我们直接来看pipe方法是怎么实现的。

[javascript] view plain copy
  1. pipe: function( fnDone, fnFail, fnProgress ) {  
  2.                     return jQuery.Deferred(function( newDefer ) {  
  3.                         jQuery.each( {  
  4.                             done: [ fnDone, “resolve” ],  
  5.                             fail: [ fnFail, “reject” ],  
  6.                             progress: [ fnProgress, “notify” ]  
  7.                         }, function( handler, data ) {  
  8.                             var fn = data[ 0 ],  
  9.                                 action = data[ 1 ],  
  10.                                 returned;  
  11.                             if ( jQuery.isFunction( fn ) ) {  
  12.                                 deferred handler ; font-weight: bold; background-color: inherit;”>function() {//为deferred增加回调函数  
  13.                                     returned = fn.apply( this, arguments );  
  14.                                     if ( returned && jQuery.isFunction( returned.promise ) ) {  
  15.                                         returned.promise().then( newDefer.resolve, newDefer.reject, newDefer.notify );  
  16.                                     } else {  
  17.                                         newDefer action + “With” ; font-weight: bold; background-color: inherit;”>this === deferred ? newDefer : this, [ returned ] );  
  18.                                     }  
  19.                                 });  
  20.                             } else {  
  21.                                 deferred handler ;//为deferred增加失败回调函数,函数为defer的执行  
  22.                             }  
  23.                         });  
  24.                     }).promise();  
  25.                 }  

先从第二行看,每次执行pipe方法,都返回了另一个deferred对象,而每个deferred对象,无非就两种方法,一是往里面加方法,二是执行里面的方法,你可以看成跟自定义事件差不多,只不过deferred对象是基于CallBacks(回调函数列表)的,功能更强大,但是原理跟自定义事件差不多,都是基于发布-订阅模式。有了这个概念,再来往后看。

jQuery.Deferred传入一个函数,会在jQuery.Deferred最后执行这个函数,并且会传入参数本deferred对象,执行环境也会被修改为本deferred对象,但是不要忘了,现在还在第一个deferred对象的函数体内,也就是这个函数既能访问到第一个deferred对象,又能访问到新建这个deferred对象。


然后我们抛开each不看,直接看each的第二个参数,hanlder为”done”,”fail”,”progress”,   data为对应的数组,然后往下看,fn为pipe调用时传进来的函数,action为对应的执行方法名,你可以看作是自定义事件里的fire,让所有函数执行,只不过有三个自定义的事件。而returned就是pipe传进来的函数的执行结果。


后面都以done举例:


然后看deferred handler {})这句话

这里的deferred为第一个deferred对象,往第一个deferred对象里加回调函数。也就是当异步队列(deferred)resolve的时候会执行function里面的代码.那到底执行了什么呢?


returned = fn.apply( this, arguments );

这句话,就是把传进来的函数执行一次,而且这个this取决与第一个derferred触发的时候执行环境是什么,而arguments就是触发第一个deferred的时候传进的实参。


if ( returned && jQuery.isFunction( returned.promise ) ) {
returned.promise().then( newDefer.resolve, newDefer.reject, newDefer.notify );
}

如果返回值拥有promise方法,说明是deferred对象或者是promise对象,promise对象就是去掉了执行方法的deferred对象,跟deferred对象差不多,因为一般返回的都是自己执行,不需要在外部执行。

先将返回值变成promise对象,然后把第二个deferred对象的执行方法放到返回promise的函数列表里。可能这里有点晕了,那就画图来解释。


现在涉及三个deferred对象,第一个deferred对象执行,将导致第二个传进去的function执行,如果这个function返回值为deferred,则把第二个deferred的执行方法,也就是resolve等方法放在第三个deferred的函数列表中,也就是第三个deferred执行将导致第二个deferred执行,而第二个deferred的执行时机则是自己控制的。这样就实现了异步队列。


而当返回值不是deferred时,直接执行第二个deferred对象,且参数为返回值。


这里利用作用域链让其拥有第一个deferred的引用,又用形参和apply让其拥有第二个deferred的引用,这样当自己返回一个deferred的时候,就能有效的将三个deferred对象链接起来,形成一条异步的职责链模式,是非常巧妙的。在这里充分运用了闭包创造对象的方法,要是改用构造函数的方式,则需要把第一个和第二个deferred用变量存起来,然后再来操作各个deferred对象,虽然也是可以实现的,但是可能就没这么巧妙了。






另外一个例子,来自百度知道:

关于javascript中函数闭包和构造函数返回值的问题?

请看上面这段代码,为什么会出现两种不同的结果?还有如果test()作为构造函数有返回值会是什么情况?返回值是基本类型的值是什么结果?返回值是引用类型会是什么结果?求高手解答,感谢!

茫无边 | 浏览 776 次  2013-09-10 15:03
2013-09-10 15:39最佳答案

首先,

1
var m = new test();

用test构造函数来实例化一个对象。执行到第17行的时候:

1
this.age = 50;

设置了一个当前对象的属性age=50;

但是,第18行:

1
2
3
return function (){
    return this.age;
}

相当于,返回了这个匿名函数作为当前对象(m)。也就是覆盖了由test函数构造的对象。也就是age已经没有了。这时,看看m.age。会是undefined。

而第23行:

1
m();

就是执行上面返回的匿名函数。this是什么? this是window对象。那this.age是什么呢?就是最上面定义的100;

对于第二种情况,代码25行:

1
var n = test();

就是一个普通的执行函数。那么在第17行:

1
this.age = 50;

发生了什么呢?首先,确定this,也就是当前作用域什么什么。由于不是构造函数执行。那么,普通的函数执行的作用域应该为window。也就是第17行,相当于把window的age属性设置为50了。也就是最上面定义的变量。

那在执行26行:

1
n();

和执行23行代码效果一样。因为age在执行17行的时候已经变成50了。so~就是50了。

其他回答

楼下答的基本正确。但是比较啰嗦,反而难懂了。

new运算符的时候会生成一个对象,叫做空对象。然后调用new运算符后面的函数,也就是类,
执行的过程,会把this绑定到空对象上。所以第一个把this是绑定到了生成的空对象上。
所以这个空对象有了age=50的属性。但返回的是函数,不是这个对象。函数中的函数这是闭包,闭包里面的函数的this指向跟是不是闭包没有关系,它依然指向window,于是,你的第一个显示100.

第二个没有生成对象。依然是闭包跟this没有关系,两个函数的调用都指向了window,所以第一次执行 修改为50了,第二次执行显示50。

这也是JS的难点之一,祝福你早点掌握~
 本回答被网友采纳
书风笑  | 2013-09-10 15:45
评论 
3 0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var age=100;
function test()
{
    this.age=50;
    return function()
    {
        return this.age;
    }
}
var m=new test(); 
//构造函数改变上下文this变为m;改变了m的age值为50,此时m=function(){return this.age}
alert(m());
//此时上下文环境为window,得出window.age=100;
var n=test();
alert(n());
//等同于alert(test()())
//因为上下文环境一直是window,test()时改变了age的值为50,所以test()()时返回50
 
//如果构造函数中返回的是非对象,则被忽略
//如果返回的是对象,则this指向返回的对象,但是原型链会出错。