扑朔迷离的 this关键字

在Java,C#等面向对象的语言中,this的含义是明确且具体的,固定指向运行时的当前对象。而由于Javascript的动态性(边解释边执行),this的指向在运行时才能确定,跟定义在哪儿无关。


this总是返回一个对象,就是属性或方法 当前 所属的对象。一般来讲,就是“.”前的那个对象(个人认为这种说法是非常容易误导人的)


1
2
3
4
5
6
7
8
9
10
var  name =  '张三' ;
var  obj = {
     name:  '李四' ,
     introduce:  function (){
         console.log( 'my name is'  this .name);
     }
};
obj.introduce();
var  f1 = obj.introduce;
f1();


在这个例子中,obj.introduce() "."左边是obj,所以this指向obj这个对象。introduce方法里面的this.name沿着作用域链往上找就是“李四”;

我们都知道函数后面带括号表示执行,不带括号就是把函数指针赋值给变量,但不执行函数,所以f1其实就是指代 function(){console.log('my name is' + this.name);},不妨可以console出来验证一下。f1()是在全局window对象中,所以这里的f1()其实就是window.f1(),也就是说"f1()"打印输出的应该是全局变量“张三”


wKioL1ga7wjTlQSjAABTseIHocg117.png


关于刚才提到的f1函数,我们可以进一步验证下

1
2
3
4
function  f1(){
     return  this ;
};
console.log(f1() === window);

wKioL1gbAPHyPA3vAAAuVy_LWt0466.png


在这里,可以得出一个结论,如果一个函数运行在全局对象中,那么,this就是指向顶层对象,在浏览器中即为window对象。


基于前面的分析,我们把上例稍微改变一下

1
2
3
4
5
6
7
8
9
10
11
12
13
var  obj = {
     name:  '李四' ,
     describe:  function  () {
         console.log(  'my name is' this .name);
     }
};
 
var  obj2 = {
     name:  '张三'
};
 
obj2.describe = obj.describe;
obj2.describe();


this指向obj对象,这点已经分析过了。这个例子中“obj2.describe = obj.describe;”说白了就是给obj2对象添加一个describe方法,this同样指向当前所在的对象,所以输入的就是“张三”,obj2对象实际变成了

1
2
3
4
5
6
obj2 = {
     name:  '张三' ,
     describe:  function  () {
         console.log(  'my name is' this .name);
     }
};

wKiom1gbBHrAlQU1AABPs07KqrU702.png


this的使用场合

1、全局环境         this--->指向全局对象 window

全局中的this就是顶层对象window

wKiom1gbCCLAgDy0AABBX_EhLWc978.png


这个例子说明,不管this是不是在函数内部,只要是在全局环境下运行,this都指向window


这里需要稍微提示下,在严格模式下,全局中的this就不再是window对象了,而是undefined

wKioL1gb69ziIMaZAAA8JyaIAV0477.png


2、构造函数       this--->指向新对象

构造函数中的this,指向生成的新对象

1
2
3
4
5
6
var  a = 3;  //全局变量a
function  Test(){
     this .a = 5;   //对象属性a
}
var  obj =  new  Test();
console.log(obj.a);

上面代码定义了一个构造函数Test,同时创建了Test的实例对象obj,我们知道通过new操作符创建构造函数的新实例对象,主要经历了4个步骤:

    (1)、创建一个新对象

    (2)、将构造函数的作用域赋给新对象(因此this就指向了这个新对象)

    (3)、执行构造函数中的代码(为这个新对象添加属性)

    (4)、返回新对象


根据这4个步骤,obj对象其实就是

1
Test {a: 5}


所以,很显然obj.a打印出来的a就是5,而不是3了

wKiom1gb95CCG6sPAABBnNEhDJc559.png


再来看一个例子:

1
2
3
4
5
6
7
8
9
10
11
var  Obj =  function  (p) {
     this .p = p;
};
 
Obj.prototype.m =  function () {
     return  this .p;
};
var  o =  new  Obj( 'Hello World!' );
 
o.p; 
o.m();


首先创建了一个构造函数Obj,给Obj的原型对象添加了一个方法m,m同样返回属性p,然后创建一个Obj的实例对象o,基于前面的分析,最终输出的就应该是两个'Hello World!'了

wKiom1gb-ijjAUKuAABv6Hm05CI730.png


3、对象的方法         this--->指向当前这个对象

当A对象的方法被赋予给B对象,该方法中的this就会从指向A对象变成了指向B对象。所以需要特别注意,将某个对象的方法赋值给另一个对象,会改变this的指向


先来验证第一句话,作为对象的方法被调用,this指向当前这个对象

wKiom1gb_SuS5nMNAAAzq17D1-g584.png

由此说明,obj.foo方法执行时,它内部的this指向了当前对象obj。先来看一个例子:

1
2
3
4
5
6
7
8
9
function  test(){
     console.log( this .a);
}
var  a = 3;  //声明一个全局变量a
var  obj = {};  //创建一个对象obj
obj.a = 5;   //给obj添加一个属性a
obj.m = test;   //给obj添加一个方法m,并且把test指针指向了m
console.log(obj);
obj.m();

wKioL1gcB63wtcn9AAAh9OW_g24650.png


再来看一个例子:

1
2
3
4
5
6
7
8
9
10
var  name =  "sky" ;
var  obj = {
     name: "zt" ,
     say: function (){
         console.log( "I am " + this .name);
     }
}
obj.say(); //I am zt
var  fn = obj.say;
fn(); //I am sky

obj.say()这个就不需要多说,在对象自己的方法里面,this指向当前的对象obj,所以是zt

把obj对象的say方法赋予给另一个fn函数,所以this会从obj指向fn,而fn运行在全局环境中,所以输出的全局变量sky

wKioL1gcFpzwEDxMAABQQVmFYB4871.png


4、内部函数(匿名函数)   匿名函数的执行环境具有全局性,this对象通常指向window

1
2
3
4
5
6
7
8
9
10
var  name =  'Window' ;
var  obj = {
     name:  'My Object' ,
     getName:  function (){
         return  function (){
             return  this .name;
         }
     }
};
console.log(obj.getName()());  //Window

按照上面的说法,似乎可以很快确认输出的是Window。我们不妨做下拆解,执行obj.getName(),得到的是一个匿名函数function(){return this.name;},此时,这个匿名函数运行在全局环境中,再次执行这个函数,里面的this自然就指向了全局Window

wKiom1gcGqeSyQJ5AABQzELg88o089.png


在定时器里面的this是否也是指向window呢?

1
2
3
4
5
6
7
8
9
10
var  name =  "sky" ;
var  obj = {
     name: "zt" ,
     say: function (){
         setTimeout( function (){
             console.log( "I am " + this .name);
         },3000);
     }
};
obj.say();


可以看到,执行say方法,其实就是一个一次性定时器,3s之后执行function(){console.log("I am"+this.name);},可能按照我们前面的说法“.”前的对象是obj,所以this指向obj,这个没错,但是,这是say方法里的this,定时器的代码是在全局作用域window对象中执行的,所以这里面的this指向window,当然严格模式下是undefined,所以最终输出的是全局的sky而不是obj对象里面的zt

wKioL1gcIbTAL5D7AACf5MT2-M8676.png


从上面的两个例子中可以看到,getName和say两个方法中,内层函数中的this没有按预想的指向外层函数对象,而是指向全局对象window,那如果我们需要指向外层函数对象,怎么办呢?这里就需要用到我们常见的“留住this”

1
2
3
4
5
6
7
8
9
10
11
var  name =  "sky" ;
var  obj = {
     name: "zt" ,
     say: function (){
         var  that =  this //把this保存到一个变量中
         setTimeout( function (){
             console.log( "I am " +that.name);
         },3000);
     }
};
obj.say();  //I am zt
1
2
3
4
5
6
7
8
9
10
11
var  name =  'Window' ;
var  obj = {
     name:  'My Object' ,
     getName:  function (){
         var  me =  this //this指向obj对象,我们把这个this放到一个变量中保存起来
         return  function (){
             return  me.name;  //成功留住了this
         }
     }
};
console.log(obj.getName()());  //My Object


最后,再来看一个经典的面试题

1
2
3
4
5
6
7
8
9
10
11
12
var  x=5,o={
     x:10,
     doit: function  doit(){
         var  x=20;
         setTimeout(
                 function (){
                     console.log( this .x);  //5
         }, 10);
     }
};
console.log(o.doit()); //undfined
( function (){console.log( this .x)})(); //5


参考上面的第4条,可以很容易得出结论 匿名函数 “function(){console.log(this.x)}”运行在全局window中,所以输出的两个5没有问题,关键是为什么o.doit()打印出来是undefined,原因很简单,doit方法没有return任何东西,它是没有返回值的。那如果给它加一个返回值呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
var  x=5,o={
     x:10,
     doit: function  doit(){
         var  x=20;
         setTimeout(
                 function (){
                     console.log( this .x);  //5
         }, 10);
         return  this .x;  //用在对象的方法中,this指向当前的对象o
     }
};
console.log(o.doit()); //10  返回的是"return this.x;"
( function (){console.log( this .x)})(); //5


本文转自   frwupeng517   51CTO博客,原文链接:http://blog.51cto.com/dapengtalk/1869458