理解javascript this关键字的在不同情况下的表现

The best time for new beginnings is now

this的指向问题在javascript中一直比较困扰我。 一下总结了几种情况下this分别指向的内容。 总的来说就是根据执行环境或函数的执行方式不同,this的值有所不同。
这里假设执行环境是在浏览器端,所有的全局对象都是指window对象

定义在全局模式下

{
    console.log(this === window); //true; 未定义任何函数的情况下,this 指向window
}

定义在函数内部

根据函数的调用方式不同,this的值可能不同

普通调用

{
    a = function() {
        return this;
    }

    b = function() {
        "use strict"
        return this;
    }

    a(); // 非严格模式: this指向全局对象
    b(); // 严格模式: this = undefine;
}

对象内部调用

需要注意的是this的指向总是与他最近的执行环境相关。 看下面的列子:

{
    o = {
        a: function() {
            return this;
        },

        b: {
            c: function() {
                return this;
            }
        }
    };

    o.a(); //作为对象的方法被调用, this指向调用该方法的对象本身,即o

    o.b.c(); // c是作为o.b这个对象的方法被调用的,所以c返回的this是指向o.b这个对象的


    //再看下面的例子

    d = o.b.c;
    d(); // d是在全局环境下调用的,所以返回的this是指向window对象的
}

但是如果是arrow function 定义的函数, 其this会被绑定到该函数外层执行环境的this上, 看下面的例子:

{
    x = 10;
    o2 = {

        x: this.x, // 此时的this是指向window对象的

        e: ( () => this),

        f: function() {
            var g = (() => {
                console.log(this.e());
                return this;
            });
            return g();
        }
    };

    o2.e(); //window
    o2.f(); //Object{}
}

执行o2.e()返回window对象。因为e的外层是o2, o2的this指向Global,所以e() 返回的this 是window对象。

o2.f 内部又定义了g 函数。该函数验证了两件事:

  1. g 本身的this指向;
  2. 不同环境下调用e(), 其this返回值会不会变化;

因为g的外层是f函数,f的this是指向o2对象本身的。所以g()返回的this是o2对象本身。也正是因为如此,如果 console.log(this.e()) 才能够成功调用e(), 如果this不是执行o2, 那么 console.log(this.e()) 就会报 Uncaught TypeError: this.e is not a function(…)。 另外this.e()的返回结果仍然是window对象,这就说明了arrow function 的另一个特性,arrow function 的this在创建的时候就指定了(即其外层执行环境的this上)。当然如果可以指定其外层执行环境,那就另当别论了。

{
    // 这里修改一下o2.f
    o2.f = function() {
        var g = (() => {
            return this;
        });
        return g();
    };

    o3 = {};

    o2.f.apply(o3);
}

这里通过apply 调用o2.f, 使of.f的this指向o3, 这样一来g()返回的this就指向了o3 本身。 去掉 console.log(this.e());这行代码的原因就是为了避免报Uncaught TypeError: this.e is not a function(…)错误(因为o3上没有定义e这个函数);

通过new调用

通过new调用的函数作为一个构造函数被执行去创建一个特定的实例对象。它主要做了3件事:

  1. 创建一个新对象,继承其构造函数的原型链;
  2. 执行该构造函数并把this绑定到新创建的对象上;
  3. 如果构造函数有返回对象则将刚返回对象作为new表达式的执行结果(),如果未返回对象,则将第一步创建的对象作为最终结果。

    {

    var Person = function(a, b) {
        this.name = a || "";
        this.age = b || "";
    };
    
    var AnotherPerson = function(a,b) {
        this.name = a || "";
        this.age = b || "";
        return {name: "Simpson"};
    };
    
    p1 = new Person("Jane", 22); // {name: "Jane", age: "22"};
    p2 = new AnotherPerson("Jane", 22); // {name: "Simpson"};
    

    }

像上面的例子,this始终是指向第一步创建的对象,当构造函数没有返回对象时,p1得到的结果即是第一步创建的对象被赋值后的结果。 当构造函数具有返回对象时, this.name = a || ""; this.age = b || "";其实也执行了,第一步创建的对象其实也被赋值了,只是最终结果被其返回值覆盖了。

通过call, apply调用

前面的例子已经提到过,通过apply可以指定function内部的this指向一个特定的对象,通过call方法也是一样的,只需将特定的对象作为参数传递进去,函数内部的this就会指定到这个特定的对象上。

{

    var k = 1;

    function j() {
        return this.k;
    }

    var l = {
        k: 10
    };

    j(); // 1;  this指向window对象
    j.apply(l); //10
    j.call(l); //10
}

通过bind指定对象调用函数

bind和call,apply的作用类似,也是将function内部的this绑定到一个特定的对象上。

{

    var k = 1;
    function j() {
        return this.k;
    }

    var l = {
        k: 10
    };

    m = j.bind(l);

    j(); // 1
    m(); // 10
}