一言以蔽之
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.