好难。。这个适合深夜一个人阅读!!!
大家都知道,得到对象有两种方式,第一是构造函数,第二是闭包,两种方式各有优点,而且用的情景也是不一样的,但是从“外观上”看,有个比较醒目的区别就是:闭包创建的对象不必用new。我们先来看看两者的具体区别:
构造函数:
优点:
1.理解起来相对较简单
2.可以实现继承
3.原型方法用的同一块内存
缺点:
1.如果没有做特殊处理,必须要用new运算符
2.无法实现私有属性,所有属性都会暴露在对象中
闭包:
优点:
1.无需使用new运算符
2.可以利用作用域干很多事情
3.可以将不想被外部看到的属性封装在闭包内部
缺点:
1.比较不好理解
2.不好实现继承
3.无法运用apply和call,因为是利用作用域实现的。
光说没意思,先来个例子先:
构造函数方式:
闭包方式
在jquery内部中,deferred对象,jqXHR都是采用的闭包的方式,而主构造函数、Event事件对象采用的是构造函数的方式。
我们先说说deferrd对象,说之前先看看是怎么用的,最值得一提的就是jq的pipe方法,当然后面变成then方法。由于我看的源码是1.7.1版本的,所以就叫pipe方法吧,这个方法可以说是deferred的精髓。
结果:
打开浏览器后:1s 后 打印1 再1s后 打印2 3 3
对于单线程的js来说,学会异步编程是非常重要的,而这里也使用职责链的模式让结构更加清晰,对于deferred整体是怎样实现的我就不一一赘述,我们直接来看pipe方法是怎么实现的。
先从第二行看,每次执行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对象,虽然也是可以实现的,但是可能就没这么巧妙了。
首先,
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的难点之一,祝福你早点掌握~
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指向返回的对象,但是原型链会出错。 |