javaScript中的This关键字

一言以蔽之

JavaScript中this关键字的应用一直是个难点,但是只需要记住一点:this始终定义在函数中,当这个函数被调用的时候,this会执行调用该函数的那个对象。

1
2
3
4
5
6
7
8
9
var person = {
    firstName: "Qiushi",
    lastName: "Li",
    fullName: function () {
        console.log(this.firstName + " " + this.lastName);
        console.log(person.firstName + " " + person.lastName);
    }
};
person.fullName(); // 会输出两行 "Qiushi Li"

上面的代码定义了一个person对象,fullName方法是其中一个property,this定义在呢fullName方法中。在这个方法没有被person对象调用之前,this不指向任何对象,当执行person.fullName()后,this指向person对象,所以会输出person对象的firstName和lastName。下面再看一个类似的例子,在浏览器中执行jQuery代码:

1
2
3
4
$("button").click(function (event) {
        console.log($(this).prop("name"));
    }
);

这里的this定义定义在一个匿名函数里,这个匿名函数又作为click的参数传递。在jQuery里,系统默认把this和调用click的对象绑定在一起,所以这里的this指向的是button对象,这和JavaScript稍微有点不同。

this在不同情况下的指向问题

1. 在全局范围内使用this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var firstName = "Qiushi", lastName = "Li";
function showFullName() {
    console.log(firstName + " " + lastName);
}

var person2 = {
    firstName: "Eric",
    lastName: "Chho",
    showFullName: function () {
        console.log(this.firstName + " " + this.lastName);
    }
};
showFullName();         // Qiushi Li
window.showFullName();  // Qiushi Li
person2.showFullName(); // Eric Chho

如果直接调用showFullName,系统会调用全局showFullName函数,在那个函数里的firstName和lastName亦是全局变量firstName和lastName。如果食用window调用,和直接调用一样,都是被全局对象,即window对象调用。最后我们食用person2对象对用其内部定义的showFullName方法,this会指向person2对象,所以this.firstName和this.lastName会是person2里的属性。

2. 在闭包里使用this

闭包,即定义在一个函数内部的函数,通常情况下,闭包能使用其外部函数里定义的属性,但是闭包不能使用外部函数的this。具体看下面这个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var user = {
    tournament: "The Masters",
    data: [
        {name: "Kevin", age: 27},
        {name: "Rashmi", age: 26}
    ],
    clickHandler: function () {
        this.data.forEach(function (person) {
            console.log("What is THIS referring to? " + this);
            console.log(person.name + " is playing at " + this.tournament);
        });
    }
};
user.clickHandler();

上面的代码会输出:

1
2
3
4
What is THIS referring to? [object global]
Kevin is playing at undefined
What is THIS referring to? [object global]
Rashmi is playing at undefined

clickHander作为user对象里的一个属性,其指向一个方法,在这个方法内部,我们首先遍历user对象的data property,这里的this指向的是调用clickHandler方法的对象,即user对象。在forEach里,我们声明了一个匿名函数,在这个匿名函数里也使用了this。第一行说明闭包里的this指向的不是user,而是global,如果在浏览器里运行,会是window对象。第二行的person.name会正确输出名字,因为这个person,即匿名函数里的参数是遍历数组里在某一时刻的某个值,但是this.tournament会输出undefined,因为这个this此时不是指向user对象,而是指向了全局对象,即global或window,但是它们均没有tournament这个property,所以会输出undefined。

解决方案

既然我们想使用外部的this,我们可以把外部的this保存在一个变量里,然后在闭包里使用这个变量就是了.虽然闭包不能使用外部函数的this,但它能使用外部函数的变量啊.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var user2 = {
    tournament: "The Masters",
    data: [
        {name: "Kevin", age: 27},
        {name: "Rashmi", age: 26}
    ],
    clickHandler: function () {
        var that = this;           // 唯一的区别
        this.data.forEach(function (person) {
            console.log("What is THIS referring to? " + that);
            console.log(person.name + " is playing at " + that.tournament);
        });
    }
};
user2.clickHandler();

上面的代码会输出:

1
2
3
4
What is THIS referring to? [object Object]
Kevin is playing at The Masters
What is THIS referring to? [object Object]
Rashmi is playing at The Masters

3. 包含this的函数被直接执行,而不是被对象调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var data = [
    {name: "Sam", age: 28},
    {name: "Kurly", age: 21}
];
var user3 = {
    data: [
        {name: "Roy", age: 26},
        {name: "Sean", age: 27}
    ],
    showData: function () {
        var rand = ((Math.random() * 2 | 0) + 1) - 1;
        console.log(this.data[rand].name + ": " + this.data[rand].age);
    }
};
var showUserData = user3.showData;
showUserData();  // 会输出sam或者kurly

因为此时虽然调用的user3内部的方法,但是因为这个方法没有被某个对象调用,所以this会指向全局对象,所以this.data会是全局对象的变量,即最上面定义的data.

解决方案

使用bind,apply等方法强行绑定this的指向,然后执行即可.

1
2
var showUserData = user3.showData.bind(user3);
showUserData(); // Sean: 27

第一句话的意思是无论在什么情况下,user3对象的showData方法里的this都指向user3对象,所以下面即使直接调用该方法,this也会始终指向user3对象.

4. this被定义在从其他地方借来的方法中

有时候,我们在定义函数或对象时使用其他地方已经定义过的方法,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var gameController = {
    scores: [20, 34, 55, 46, 77],
    avgScore: null,
    players: [
        {name: "Ruth", playerId: 100, age: 22},
        {name: "Shawnee", playerId: 101, age: 21}
    ]
};
var appController = {
    scores: [900, 845, 809, 950],
    avgScore: null,
    avg: function () {
        var sumOfScores = this.scores.reduce(function (prev, cur, index, array) {
            return prev + cur;
        });

        this.avgScore = sumOfScores / this.scores.length;
    }
};
gameController.avgScore = appController.avg(); // undefined

这里gameController你的avgScore预留为null,目的是借用appController里定义的方法来使用自己的数据去计算结果.但是avg方法里的this仍然指向appController对象,因为avg方法是被其调用的,然后更新的this.avgScore也只是appController里的那个property,所以gameController里的acgScore仍然是null.

解决方案

把avg里的this强行绑定为指向gameController.

1
2
3
appController.avg.apply(gameController, gameController.avgScore);
console.log(gameController.avgScore);  // 46.4
console.log(appController.avgScore);   // null

因为此时avg里的this指向了gameController,即使再次使用appController去调用它,其this也不会改变,所以不会更新appController里的avgScore这个property,输出仍然为null.