你的位置:首页 > Java教程

[Java教程]浅谈JS闭包和this关键字


问题背景:由于JS中闭包的存在和this的特殊性使得很多时候无法判断出变量的值。

一.闭包中的变量

 

例1.

  1. var name="The Window";
  2. var getName=(function(){
  3.         var name = "My function";
  4.         return function(){
  5.             return name;
  6.         }
  7.     })();
  8.  
  9. console.log(getName());//My function
  10. console.log(name);//The Window
  11. getName=null;//解除对匿名函数的引用,释放因闭包占用的多余内存

JS中对闭包的概念解释是:闭包是指有权访问另一个函数作用域中的变量的函数。

例1中getName右侧函数表达式就是一个立即执行函数并且返回匿名函数,这个匿名函数就是一个闭包。对于一般的函数而言,其执行结束之后就会释放其所占内存,但对于闭包就不是这样子了。getName是返回的匿名函数,它有用到外部匿名函数的name,所以立即执行函数执行结束后也是释放不了内存的,必须确定闭包不可能再执行的情况下才会释放内存(闭包占用大量内存的原因)。

为何code5的name是解析成My function而code10却解析成The Window呢?

这就要从执行环境(execution context,俗称"上下文")说起,当代码在执行环境中执行时,先创建变量对象,然后再创建变量对象(VO)的一条作用域链,保证对执行环境有权访问的所有变量和函数的有序访问。而作用域链的前端始终是当前执行的代码所在执行环境的变量对象,下一个变量对象来自下一个包含执行环境,一直延续到全局执行环境(window)的变量对象。标识符的解析也是从作用域链最前端(当前执行环境的VO)一层层解析的,直到作用域链的末端(全局执行环境的VO)。故要看变量的作用域,只要看它所在的执行环境的作用域链范围。------由于函数执行环境比较特殊,其变量对象称为活动对象(AO)。全局执行环境比较特殊,其变量对象始终存在!

执行getName(),其中name所在执行环境(闭包函数执行环境)的作用域链包含自身(即闭包函数)活动对象、外部函数(即立即执行函数)活动对象、全局变量对象,由内而外进行搜索,从外部函数活动对象找到name定义。执行console.log(name),其中name所在执行环境(全局执行环境)的作用域链只包含全局变量对象,从中找到name定义。

例2.

  1. var result = [];
  2. function createFunctions(){
  3.     for(var i=0; i<3 ;i++){
  4.         result[i]=function(){
  5.             return i;
  6.         };
  7.     }
  8. }
  9.  
  10. createFunctions();
  11. console.log(result[0]()); // 3
  12. console.log(result[1]()); // 3
  13. console.log(result[2]()); // 3

作用域链的这种配置机制会导致闭包只能取得包含函数中任何变量的最后一个值!

解决方法:

  1. var result = [];
  2. function createFunctions(){
  3.     for(var i=0; i<3 ;i++){
  4.         result[i]=(function(num){
  5.             return function(){
  6.                 return num;
  7.             };
  8.         })(i);
  9.     }
  10. }
  11.  
  12. createFunctions();
  13. console.log(result[0]()); // 0
  14. console.log(result[1]()); // 1
  15. console.log(result[2]()); // 2

使用3个立即执行函数给每个function(){return num};制作闭包,把i的副本存进num中,有3个i的副本,值分别为0,1,2,result[i]分别取对应的副本即可。

扩展:由于JS中没有块作用域的概念,但可以使用立即执行函数通过闭包的机制来制作块作用域!

例3.

  1. /*JS没有块级作用域导致,局部变量影响全局变量*/
  2. i=2;
  3. for(var i=0;i<4;i++){}
  4. alert(i);//4

例4.

  1. /*使用立即执行函数通过闭包机制模仿块级作用域,不影响外部变量*/
  2. i=2;
  3. (function(){
  4. for(var i=0;i<4;i++){}
  5. })();
  6. alert(i);//2

 

二.This的特殊性

先来个比较简单的例子:

例5.

  1. var name="The Window";
  2. var object = {
  3.     name:"My object",
  4.     getName:function(){
  5.         var name = "My Function";
  6.         return this.name;
  7.     }
  8. }
  9.  
  10. console.log(object.getName());//My object
  11. console.log((object.getName)());//My object
  12. console.log((object.getName = object.getName)());//The Window

this是基于执行环境绑定:全局函数中,this指代window;函数作为对象方法调用时,this指代那个对象;

code10和code11方式都一样,比较好理解。(注意:this.是取属性,getName中的var name="My Function"不能理解为给该函数添加属性name并且该属性的值为"My Function"!)

code12大家可能会比较困惑,为何不是输出"My object"呢"?

  1. console.log((object.getName = object.getName)());//The Window

(object.getName = object.getName)这个函数表达式的意义是:

1.为 object 设置 getName 属性/方法。

2. object.getName的值赋值给它

3.故该表达式的值就是函数本身:function(){var name = "My Funcition";return this.name;}

等同于:

  1. console.log((function(){var name = "My Function";return this.name;})());//The Window

全局函数,this为window,所以this.name为"The Window"!

例子6.

  1. var name="The Window";
  2. var object = {
  3.     name:"My object",
  4.     getName:function(){
  5.         return function(){
  6.             return this.name;
  7.         };
  8.     }
  9. }
  10.  
  11. console.log(object.getName()());//The Window
  12. console.log((object.getName = object.getName)()());//The Window

匿名函数比较特殊,this指代window!;

比较系统的分类是《JavaScript语言精粹》中的,分为函数调用模式(this绑定全局对象window)和方法调用模式(this绑定调用方法的主体)

可以看出当函数以函数调用模式使用时,this竟然是固定为window!(如code11所示),这个其实是JS语言设计缺陷,倘若语言设计正确,应该是:当内部函数被调用时,内部函数的this应该绑定到外部函数this对象!

这个设计缺陷有一个很容易的解决方案:

  1. var name="The Window";
  2. var object = {
  3.     name:"My object",
  4.     getName:function(){
  5.         var that = this;
  6.         return function(){
  7.             return that.name;
  8.         };
  9.     }
  10. }
  11.  
  12. console.log(object.getName()());//My object
  13. console.log((object.getName = object.getName)()());//The Window

把this对象赋值给变量that,使得闭包可以取到外部变量that(注意:每个执行环境都有自己的this,所以得采用这种方式来取得外部的this对象)

例7

  1. var name="The Window";
  2. getName=(function(){
  3.        var name = "My function";
  4.         return function(){
  5.             var name="function";
  6.             return this.name;
  7.         }
  8.     })();
  9.  
  10. console.log(getName());//The Window
  11. console.log((getName)());//The Window

最后检验一下:

Code11和code10是一样的,code10属于函数调用,所以this指代window