21.5疑团!“this”迷宫

21.5.1 无数个陷阱——令人困扰的“this”谜团

例21.31this代词.html

  1. <script> 
  2.     function MyClass(){ 
  3.         alert(this.constructor);     
  4.     } 
  5.     var obj = new MyClass(); 
  6.      
  7. </script> 

 

21.5.1.1 this代词的运用

首先,不一定只有对象方法的上下文中才有this这个代词,在JavaScript中,全局函数调用和其他的几种不同的上下文中也都有this代词。

 

例21.32几种不同上下文的this代词.html

  1. <script> 
  2.     function Foo(){ 
  3.         //如果this引用的构造函数是arguments.callee引用的对象 
  4.         //说明是通过new操作符执行的构造函数 
  5.         if(this.constructor == arguments.callee){ 
  6.             document.write("Object Created<br/>"); 
  7.         }    
  8.         //如果this是window,那么是全局调用 
  9.         else if(this == window){ 
  10.             document.write("Normal call<br/>");  
  11.         } 
  12.         else{//否则是作为其他对象的方法来调用 
  13.             document.write("called by" + this.constructor + "<br/>"); 
  14.         } 
  15.     } 
  16.     Foo();//全局函数调用中,this的值为window 
  17.     Foo.call(new Object()); //作为一个Object对象的成员方法来调用 
  18.     new Foo(); //被new操作符调用,执行对象构造 
  19. </script> 

 

运行结果:

Normal call
called byfunction Object() { [native code] }
Object Created

 

其次,JavaScript的一个函数可以作为任何一个“所有者”对象的方法来调用,具体的方式是采用Function.prototype.call()Function.prototype.apply()方法。

21.33 this代词的变化.html

  1. <script> 
  2.     function Foo(){ 
  3.         document.write(this.x + this.y); 
  4.     } 
  5.     //用对象{x:1,y:2}调用,相当于alert(1+2); 
  6.     Foo.call({x:1,y:2}); 
  7. </script> 

运行结果:

3

21.5.1.2 this“陷阱”

第三,在JavaScript实现的继承里,不论何种方式,this指向的总是当前类型的方法,而不是它所在的类型的方法,换句话说,如果在子类中覆盖了父类的某个方法,那么父类中其他依赖于这个子类方法的方法也会发生变化,这一点也和C++等语言有很大的不同。

21.34 this“陷阱”.html

  1. <script> 
  2. //定义Base类型 
  3. function Base(){ 
  4.     //Base类型的公有方法Foo() 
  5.     this.Foo = function(){ 
  6.         return 10;   
  7.     }    
  8.     //Base类型的公有方法Bar() 
  9.     this.Bar = function(){ 
  10.         document.write(this.Foo() + 10); 
  11.     } 
  12. //定义Drivide类型,继承Base类型 
  13. function Drivide(){ 
  14.     //Drivide类型的公有方法Foo() 
  15.     this.Foo = function(){ 
  16.         return 20; 
  17.     } 
  18.  
  19. //原型继承 
  20. Drivide.prototype = new Base(); 
  21. //构造一个Drivide对象 
  22. var d = new Drivide(); 
  23. d.Bar(); //得到30而不是20,d.Bar()的时候因为“this”引用的是Drivide类型的对象d 
  24. //所以d.Bar()执行时调用的this.Foo()是Drivide类型中定义的Foo(),尽管Bar()在Drivide中 
  25. //并没有被重载 
  26. </script> 

以上特点是基础库和框架设计中尤其要注意的问题,否则某个类库有可能会因为开发人员扩展了其基类改写派生类方法时无意中影响到了基类的其他方法。不过这个问题实际上通过将被依赖的方法间接公开的方式是可以避免的。

例21.34 this“陷阱”改进.html

  1. <script> 
  2.     //定义Base类型 
  3.     function Base(){ 
  4.         //因为_Foo方法被其他方法依赖,因此定义成私有方法 
  5.         function _Foo(){ 
  6.             return 10;   
  7.         } 
  8.          
  9.         //Base类型的公有方法Foo() 
  10.         this.Foo = _Foo
  11.          
  12.         //Base类型的公有方法Bar() 
  13.         this.Bar = function(){ 
  14.             document.write(_Foo() + 10);     
  15.         } 
  16.     } 
  17.      
  18.     //定义Drivide类型,继承Base类型 
  19.     function Drivide(){} 
  20.     //Drivide类型的原型方法Foo() 
  21.     Drivide.prototype.Foo = function(){ 
  22.         return 20;   
  23.     } 
  24.      
  25.     //原型继承 
  26.     Drivide.prototype = new Base(); 
  27.     //构造一个Drivide对象 
  28.     var d = new Drivide(); 
  29.     d.Bar();//这些得到了我们期望的结果20,因为这一次Bar依赖的是私有方法 
  30. </script> 

 

所以,一般情况下,定义方法的时候,对于相互依赖的方法,注意不要将被别的方法依赖的方法直接公开。

21.5.1.3 this代词的异步问题

我们通过0级DOM或者2级DOM的方式将方法注册为事件处理函数,结果发现"this"代词指向引发这个事件的对象,而不是指向被注册方法本身的对象。

  1. <html> 
  2. <head> 
  3.    <title>Example-21.35 this代词的异步问题</title> 
  4. </head> 
  5. <body> 
  6. <button id="btn" name="btn"/> 
  7. <script> 
  8. <!-- 
  9.   //构造一个Object对象 
  10.   var obj = {}; 
  11.   //定义obj对象的foo方法 
  12.   obj.foo = function(){ 
  13.     alert(this == obj); 
  14.     alert(this == btn); 
  15.     alert(this == window); 
  16.   } 
  17.   btn.onclick = obj.foo; 
  18.     //这个问题还比较好理解,可以认为是btn.onclick()做实际调用 
  19.         //而赋值只是将函数引用给了btn.onclick代理(false,true,false) 
  20.   btn.attachEvent("click",obj.foo);//(false,true,false) 
  21.         //而这个也不对,而且this的值是window,怎么说也是一种遗憾 
  22.   btn.attachEvent("click",function(){ obj.foo.call(btn)});//(false,true,false) 
  23.     //还得做这样的修正 
  24.   setTimeout(obj.foo,100);//(false,false,true) 
  25.     //setTime也是一样的,this的值是window 
  26. --> 
  27. </script> 
  28. </body> 
  29. </html> 

 

21.5.2 偷梁换柱——不好的使用习惯

事实上,在JavaScript中,this代词的复杂性很大程度上取决于使用方式。JavaScript具备有动态改变方法的“所有者“的能力,这个能力直接导致了运行时”this“指代结果的不明确性。

通常情况下,我们应该尽量避免将全局的函数或者闭包动态地作为不同类型对象的方法来使用,更不应该将声明为某种确定类型的方法的函数作为另一种类型的方法来调用。

例21.36不被推荐的写法.html

  1. <script> 
  2. function add(){ 
  3.   return this.a + this.b; 
  4. document.write(add.call({a:1,b:2}) + "<br/>");  //这种定义了一个函数然后作为某个具体对象方法进行调用的方式不被推荐 
  5. document.write(add.call({a:'x',b:'y'}) + "<br/>"); 
  6. function Point(x,y){ 
  7.   this.dist = function(){ 
  8.     return Math.sqrt(x*x + y*y); 
  9.   } 
  10. var p = new Point(1,2); //将p.disp()作为p2的对象方法来用,也不被推荐 
  11. var p2 = new Point(2,3); 
  12. document.write(p.dist.call(p2)); //相当于 Math.sqrt(1*1 + 2*2) 
  13. </script> 

如果确实需要让两个不同的类型共享一个或者一组方法,应当让他们具有同一个原型,而不是将同一个方法交由这两个对象来各自执行:

例21.37 共享prototype.html

  1. var abPrototype = { 
  2.     a:null,b:null,add:function(){return this.a + this.b} 
  3. //定义strCls类型 
  4. var strCls = function(a,b){ 
  5.   this.a = a; 
  6.   this.b = b;   
  7. //利用原型,值得推荐的方式 
  8. strCls.prototype = abPrototype
  9. //定义numCls类型 
  10. var numCls = function(a,b){ 
  11.   this.a = a; 
  12.   this.b = b; 
  13. }; 
  14. //numCls和strCls都以abPrototype为原型 
  15. numCls.prototype = abPrototype
  16.  
  17. //构造strCls对象 
  18. var strcls = new strCls(1,2); 
  19. //构造numCls对象 
  20. var numcls = new numCls('a','b'); 
  21. //执行原型方法add(); 
  22. strcls.add(); 
  23. numcls.add(); 
  24. </script> 

111