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


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


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函数,我们可以进一步验证下

function f1(){
    return this;
};
console.log(f1() === window);

wKioL1gbAPHyPA3vAAAuVy_LWt0466.png


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


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

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对象实际变成了

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,指向生成的新对象

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对象其实就是

Test {a: 5}


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

wKiom1gb95CCG6sPAABBnNEhDJc559.png


再来看一个例子:

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。先来看一个例子:

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


再来看一个例子:

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

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呢?

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”

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
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


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

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任何东西,它是没有返回值的。那如果给它加一个返回值呢?

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