谈JS中匿名函数的上下文环境

受一位热心网友的关注和提问,我又对JS中的匿名函数的上下文环境作了详细地分析和实验,下面把我对该问题的一点思考与大家分享:

 

    关键字 this    


    在JavaScript的对象系统中,使用关键字 this 的地方:在对象的方法(或属性)被调用时,指代调用该方法(或属性)的对象实例。举个例子:
   

Js代码  收藏代码
  1. function foo(){  
  2.     this.property = "red";  
  3.     this.method = function(){...}  
  4. }  

 

 

 

    使用 this 的原因 

 

    因为在实例化对象时,总是不能确定开发者会使用什么样的变量名。使用 this,即可在任何多个地方重用同一个函数。请思考下面的例子:

   

Js代码  收藏代码
  1. function showColor() {  
  2.     alert(this.color);  
  3. };  
  4.   
  5. var oCar1 = new Object;  
  6. oCar1.color = "red";  
  7. oCar1.showColor = showColor;  
  8.   
  9. var oCar2 = new Object;  
  10. oCar2.color = "blue";  
  11. oCar2.showColor = showColor;  
  12.   
  13. oCar1.showColor();      //输出 "red"  
  14. oCar2.showColor();      //输出 "blue"  

 
    注意,引用对象的属性时,必须使用 this 关键字。例如,如果采用下面的代码,showColor() 方法不能运行:

 

 

   

Js代码  收藏代码
  1. function showColor() {  
  2.     alert(color);  
  3. };  

 

 

 

    如果不用对象或 this 关键字引用变量,ECMAScript 就会把它看作局部变量或全局变量。然后该函数将查找名为 color 的局部或全局变量,但是不会找到。结果如何呢?该函数将在警告中显示 "null"。

 

    函数与方法  

 

    由于在JavaScript中不明确区分函数与方法。因此有些代码看起来很奇怪:

   

Js代码  收藏代码
  1. function foo() {  
  2.     // 下面的this指代调用该方法的对象实例  
  3.     if (this===window) {  
  4.         document.write("call a function.""<br />");  
  5.     }  
  6.     else {  
  7.         document.write("call a method, by object: "this.name, "<br />");  
  8.     }  
  9. }  
  10.   
  11. function MyObject(name) {  
  12.     // 下面的this指代new关键字新创建实例  
  13.     this.name = name;  
  14.     this.foo = foo;  
  15. }  
  16.   
  17. var obj1 = new MyObject("obj1");  
  18. var obj2 = new MyObject("obj2");  
  19.   
  20. // 测试1: 作为函数调用  
  21. foo();      //Output=>call a function.  
  22.   
  23. // 测试2: 作为对象方法的调用  
  24. obj1.foo(); //Output=>call a method, by object: obj1  
  25. obj2.foo(); //Output=>call a method, by object: obj2  
  26.   
  27. // 测试3: 将函数作为“指定对象的”方法调用  
  28. foo.call(obj1); //Output=>call a method, by object: obj1  
  29. foo.apply(obj2);    //Output=>call a method, by object: obj2  

 

 

 

    在测试2里,obj1/obj2对foo()的调用是很普通的调用方法。
    而测试3中的call()与apply()就比较特殊。在这个测试中,foo()仍然作为普通函数来调用,只是JavaScript的语言特性允许在call()/apply()时,传入一个对象实例来指定foo()的上下文环境中所出现的this关键字的引用。需要注意的是,此时的foo()仍旧是一个普通函数调用,而不是对象方法调用,该测试只是将foo函数的this原来引用‘window’更改为后来参数对象,不要被测试三的结果迷惑了,不信看下面例子:

   

Js代码  收藏代码
  1. function foo() {  
  2.     // 下面的this指代调用该方法的对象实例  
  3.     if (this===window) {  
  4.         document.write("call a function.""<br  />");  
  5.     }  
  6.     else{  
  7.         document.write("call a function or method.""<br />");  
  8.     }  
  9. }  
  10.   
  11. function MyObject(name) {  
  12.     // 下面的this指代new关键字新创建实例  
  13.     this.name = name;  
  14.     this.foo = function(){  
  15.         document.write("call a method, by object: "this.name, " ; and then ");  
  16.         foo();  
  17.     };  
  18. }  
  19.   
  20. var obj1 = new MyObject("obj1");  
  21. var obj2 = new MyObject("obj2");  
  22.   
  23. // 测试1: 作为函数调用  
  24. foo();      //Output=>call a function.  
  25.   
  26. // 测试2: 作为对象方法的调用  
  27. obj1.foo(); //Output=>call a method, by object: obj1 ; and then call a function.  
  28. obj2.foo(); //Output=>call a method, by object: obj2 ; and then call a function.  
  29.   
  30. // 测试3: 将函数作为“指定对象的”方法调用  
  31. foo.call(obj1); //Output=>call a function or method.  
  32. foo.apply(obj2);    //Output=>call a function or method.  

 

 

 

    测试3结果中明显不包含字符串“call a method, by object:”,说明该调用明显不是method,而是function。

    但再仔细想想,严格来说,全局foo()函数也是方法,它其实是其上下文环境(window)的方法,所以以下三种调用方式等价:
   

Js代码  收藏代码
  1. foo();  
  2. this.foo();  
  3. window.foo();  

 
    这不由得让我想到JS里面的另一个机制:函数即对象,对象即可以有属性和方法;而方法又可以是函数。这样就拧成一个类似“鸡生蛋,蛋生鸡”的循环了。

 

 

 

    署名函数和匿名函数的上下文环境  

 

    有了以上基础,再比对一下署名函数和匿名函数的上下文环境,看他们之间有什么区别之处,例子如下:

   

Js代码  收藏代码
  1. // 署名函数  
  2. function foo() {  
  3.     // 下面的this指代调用该方法的对象实例  
  4.     if (this===window) {  
  5.         document.write("foo的上下文环境(即this值)是window.""<br />");  
  6.     }  
  7. }  
  8.   
  9. // 作为函数调用  
  10. window.foo(); //Output=>foo的上下文环境(即this值)是window. 说明foo函数属于window这个上下文环境,即foo是window对象的一个方法  
  11.   
  12. this.foo(); //Output=>foo的上下文环境(即this值)是window.  说明此处的this指代的就是window对象  
  13.   
  14. foo(); //Output=>foo的上下文环境(即this值)是window. 说明foo如果本就属于这个上下文环境时,this可以省略  
  15.   
  16. //三个函数调用最终结果相同,说明全局函数的上下文环境就是window。  
  17.   
  18. // 匿名函数  
  19. (function(){  
  20. // 下面的this指代调用该方法的对象实例  
  21.     if (this===window) {  
  22.         document.write("匿名函数的上下文环境(即this值)是window.",    "<br />");  
  23.     }  
  24. })() //Output=>匿名函数的上下文环境(即this值)是window.  

 

 

 

    从上面两个函数例子对比可以说明,匿名函数的上下文环境(this)也是window,它也可以称得上是window对象的方法,只不过是匿名的,而且不能像window.foo()这样方式直接调用执行它(假设如果能像这样方式调用执行,那调用格式就该是window.匿名函数()了,当然这是开玩笑了,要不然JS规定匿名函数这一规则就没必要了),只能让它自动执行。
    在这种条件下,看来署名函数和匿名函数区别不在于上下文环境,而是调用执行的方式。
    既然两种函数的上下文环境相同,而且函数就是对象,那么在塑造两种不同函数的对象,并访问其中的属性(或方法)试试,看看二者有没有区别,例子如下:
   

Js代码  收藏代码
  1. // 署名函数  
  2. function foo() {  
  3.     this.color = "red";  
  4. }  
  5.   
  6. var f = new foo();  
  7. alert(window.f.color);  // Output=>red  
  8. alert(this.f.color);        // Output=>red  
  9. alert(f.color);     // Output=>red  
  10. alert(window.color);        // Output=>undefined  
  11. alert(this.color);          // Output=>undefined  
  12. alert(color);       // Output=>js error:color未定义  
  13. // 六种调用最终结果对比,说明对象f的上下文环境不是window,而是window树结构的一个分支,应该说f属于window。  
  14.   
  15. // 匿名函数  
  16. (function(){  
  17.     this.color="blue";  
  18. })();  
  19.   
  20. alert(window.color);        // Output=>blue  
  21.   
  22. (function(){  
  23. alert(window.color);        // Output=>blue 说明匿名函数的上下文环境是window  
  24. alert(this.color);      // Output=>blue 在window中的变量被称为全局变量,可以省略this  
  25. alert(color);       // Output=>blue 尽管可以省略this,但不建议用这样的方式,因为如果该匿名函数存在一个同名的局部变量,那么它调用的就不再是全局变量了  
  26. })();  
  27. // 通过对匿名函数属性的调用方式及结果对比,更坚定一个事实:匿名函数的上下文环境是window!  

 

 

 

    通过上面的详细举例,相信您一定明白了。以上如有错误,请多批评指正!

posted on 2014-03-14 10:40  innoo1  阅读(332)  评论(0)    收藏  举报

导航