无论新入行的开发者,还是老手,也时常会被关键词“this”所迷惑。这篇文章的目标就是全面地解释this。当你读完这篇文章的时候,this将不再是你在JavaScript领域中的一个难题。我们将会在每个例子中了解如何使用this,包括最棘手最难以捉摸的部分。
我们使用this类似于我们在日常交谈中使用代词一样。我们会写到“约翰跑得很快因为‘他’正在赶火车”。
注意到这里的“他”。我们也可以写成“约翰跑得很快因为‘约翰’正在赶火车”。我们一般不会重复使用人名“约翰”,我们如果这样做了,我们的家人和朋友肯定会觉得我们脑子出了问题。在同样优雅的语言,JavaScript中,我们运用“this”作为一个捷径,或者指示物;它指示着一个对象,相当于语境中的主语,或者执行代码时的对象。
看看以下的例子:
var person = {
firstName: "Penelope",
lastName: "Barrymore",
fullName:function () {
//注意这里我们可以使用this就跟我们例句中的“他”一样
console.log(this.firstName + "" + this.lastName);
//我们也可以这样子写:
console.log(person.firstName + "" + person.lastName);
}
}
在以上例子中,如果我们使用person.firstName和person.lastName,这段代码会变得模糊不清:如果已经有一个全局变量person,当我们调用person.firstName的时候可能会访问到全局变量person中的属性,同时也给排除故障带来了困难。所以我们运用this来使得我们的代码美观的同时,还更为精确。
就像例句中的代词“他”指代着它的对象,this也指代着使用它的函数中的执行对象。this不但指代着它的对象,同时还包含着指代目标对象的值。就像代词一样,this也可以理解为语境中调出对象的捷径。
JavaScript关键词this基础知识
首先,所有JavaScript的函数都有属性,就像所有的对象都有属性一样。当一个函数被调用时,它就会拉取this属性:一个变量包含了函数执行时对象的数值。
this只会调用单个对象((并包含该目标的值)。注意,当我们运用到严格模式的时候,this在全局函数和匿名函数中保留未定义的值是不会上行到任何对象的。
当this在某个函数(假设函数A)中被调用时,它包含了被函数A调用对象的值。我们需要通过this来获取被调用对象的属性,特别是当我们调用对象没有名字的时候。实际上,this也仅仅是一个为调用对象提供的捷径。
再来看一次this最基本的例子:
varperson = {
firstName:"Penelope",
lastName:"Barrymore",
//当this在“showFullName”函数中被调用,且该函数被定义在了person对象中
// this就会包含了对象person的值因为person在showFullName被调用了
showFullName ()
showFullName: function () {
console.log(this.firstName + " " + this.lastName);
}
}
person.showFullName();//输出Penelope Barrymore
同时也来看看jQuery下this的例子:
//一段非常普通的jQuery代码
$ ("button").click (function(event) {
// $(this)将会附有按钮($("button"))对象的值
//因为按钮对象被click ()函数调用了
console.log($ (this).prop ("name"));
});
jQuery语法中的$(this),与JavaScript中关键字this有一样的功能。这里的$(this)运用在了一个匿名函数中,并且这个匿名函数在按钮click()上。$(this)上行到了按钮对象,是因为jQuery库中,$(this)被绑定在了被click()调用的对象上。因此,即使$(this)在匿名函数中被定义了,它也会含有jQuery按钮($(“button”))对象的值。
注意按钮在HTML中属于DOM元素,同时也是一个对象,在上述例子中它就是一个jQuery对象因为我们把它包装在jQuery $()函数中
有关this的“原来如此”
如果你了解下面这个规则,你就会非常清晰的了解this: this只会在定义this的函数调用了某个对象时才会被附上值。这里我们就把定义this的函数称为“this函数”吧。
即使看上去this出现在了对象被定义的地方,this在“this函数”被调用前都不会被真正地附上值,而且它的值仅仅决定于被“this函数”调用的对象。绝大多数情况下,this都会附上被调用对象的值,然而在少量情况下,this不会附上被调用对象的值。稍后的文章中会提到。
this用在全局作用域中
当代码运行在全局作用域中,所有全局变量和函数都被定义在了window对象中,因此当我们将this运用在全局函数中时,它指代(并含有)页面JavaScript主容器window对象的值(不包括严格模式,在之前有提到过)。
例如:
var firstName = "Peter",
lastName = "Ally";
function showFullName () {
console.log(this.firstName + " " + this.lastName);
}
//这个函数里的this就含有window对象的值
//因为showFullName ()函数被定义为了全局函数,就像firstName和lastName一样
//下面的this指代了person对象
//因为showFullName()函数被定义在了person对象中
var person = {
firstName:"Penelope",
lastName:"Barrymore",
showFullName:function() {
console.log(this.firstName + " " + this.lastName);
}
}
showFullName (); // Peter Ally
// window对象是所有全局变量和全局函数被定义的地方,所以:
window.showFullName (); //输出Peter Ally
//在对象person中定义的函数showFullName()中的this依旧指代 对象person
person.showFullName(); //输出PenelopeBarrymore
this最迷惑人的几个地方
以下几种情况是this最为迷惑的情况:当我们借用函数中包含了this,当我们用含有this的方法定义变量,当一个函数用this去传递回调函数和当this出现在闭包里的时候。我们将会一一举例说明并弄清楚this在这些情况下的用法。
一、当this去传递回调函数
当我们使用一个带有this的方法作为一个参数出现在回调函数中的时候,事情将会变得复杂:
//下面有一个单一对象,还有一个会被页面按钮触发的方法clickHandler
var user = {
data:[
{name:"T. Woods", age:37},
{name:"P. Mickelson", age:43}
],
clickHandler:function(event) {
varrandomNum = ((Math.random () * 2 | 0) + 1) - 1; // 0到1之间的随机数
//下面这一行代码会在上面的data数组中随机抽取人名和年龄
console.log(this.data[randomNum].name + " " + this.data[randomNum].age);
}
}
//按钮被包装在了jQuery里面,所以现在它是一个jQuery对象
//但是会输出undefined因为在按钮对象上没有任何数据
$("button").click (user.clickHandler); //报错:Cannot read property '0' of undefined
在以上的代码中,因为按钮($(“button”))本身就是一个对象,这里将方法user.clickHandler作为了回调函数传递数据,我们知道当this在user.clickHandler方法中时,它将不会指代对象user。this仅会指代user.clickHandler里面的对象,因为this被定义在了这个方法里面。而user.clickHandler被按钮所调用,所以user.clickHandler将会在按钮的点击时被执行。
注意到即使我们利用代码user.clickHandler调用了clickHandler()方法,这个方法本身将会被按钮调用,而且在这个语境中,this指代对象是按钮对象($(“button”))。
在这里我们可以很明显的看到语境的变化:当我们在其他对象上执行一个方法,而不是在这个对象当初被定义的地方的时候,this将不会指代原来的对象,而是会指代着被定义this的方法所调用的对象。
当我们想让this.data指代对象data中的属性时,我们可以用到bind(),apply()和call()来对this的值做出指定
要解决这个问题,我们可以使用方法bind():
把下面这行代码:
$ ("button").click(user.clickHandler);
改写成:
$ ("button").click(user.clickHandler.bind (user));
二、当this在闭包中
另外一个容易混乱的地方就是当this用在了闭包中。非常重要的一点,闭包是没办法获取到闭包外其它函数中的this,因为this只会被定义它的函数本身所获取。
varuser = {
tournament:"The Masters",
data: [
{name:"T. Woods", age:37},
{name:"P. Mickelson", age:43}
],
clickHandler:function () {
//这里运用this.data是没有问题的
//因为this指代了对象user,所以this拥有了对象user的属性
this.data.forEach(function (person) {
//但是在这个匿名函数中,this不再指代对象user
//因为闭包没办法获取外在函数中的this的值
console.log ("What is This referringto? " + this); // window对象
console.log (person.name + " isplaying at " + this.tournament);
// T. Woods is playing at undefined
// P. Mickelson is playing at undefined
})
}
}
user.clickHandler();//现在this就指代了window对象
在匿名函数中的this不能获取外在函数中this的值,所以它上行到了全局对象window。
若要修正这段代码,我们只需要在进入函数forEach之前将this的值赋值到另外一个变量上:
var user = {
tournament:"TheMasters",
data:[
{name:"T. Woods", age:37},
{name:"P. Mickelson", age:43}
],
clickHandler:function(event) {
//若要获取当this还指代着对象user时候的值,,我们要在这里设置另外一个变量
//把this的值赋值到了theUserObj变量上
vartheUserObj = this;
this.data.forEach(function (person) {
//我们现在使用的是theUserObj.tournament
console.log (person.name + " isplaying at " + theUserObj.tournament);
})
}
}
user.clickHandler(); //输出T. Woods is
playing at The Masters //输出P. Mickelsonis playing at The Masters
三、当含有this的函数被用做定义变量
当我们用函数去定义一个变量时,this会上行到其他对象上:
//这里的变量data是一个全局变量
var data = [
{name:"Samantha",age:12},
{name:"Alexis",age:14}
];
var user = {
//这里的变量data是对象user的属性
data:[
{name:"T.Woods", age:37},
{name:"P.Mickelson", age:43}
],
showData:function(event) {
varrandomNum = ((Math.random () * 2 | 0) + 1) - 1; //0到1的随机数字
//这一行代码会在data数组中随机抽取并输出一个人名
console.log(this.data[randomNum].name + " " + this.data[randomNum].age);
}
}
//将user.showData定义到一个变量中
var showUserData = user.showData;
//当执行showUserData函数的时候,控制台输出值是从全局变量数组data中抽取出来的
//而不是对象user中的data数组
showUserData(); // Samantha 12 (从全局变量数组抽取)
要修改这段代码,我们也可以用到绑定值的方法bind()将this赋值:
//将showData方法绑定在了对象user上
var showUserData = user.showData.bind(user);
//现在我们可以获取user对象的值了,因为this上行到了user对象上
showUserDat(); //输出P. Mickelson 43
四、当this出现在借用函数中
借用函数在JavaScript开发中十分常见,作为一个JavaScript开发员,我们会时常见到这样的例子。这也是我们需要掌握的一个地方。
让我们看下当this出现在借用方法的情况:
//这里有两个对象,其中一个包含了avg()函数,另一个则没有
//所以我们借用了avg()函数
var gameController = {
scores:[20, 34, 55, 46, 77],
avgScore:null,
players:[
{name:"Tommy",playerID:987, age:23},
{name:"Pau",playerID:87, age:33}
]
}
var appController = {
scores:[900, 845, 809, 950],
avgScore:null,
avg: function () {
varsumOfScores = this.scores.reduce (function (prev, cur, index, array) {
return prev + cur;
});
this.avgScore= sumOfScores / this.scores.length;
}
}
//如果我们运行下面这段代码
// gameController.avgScore属性将会被赋上对象appController里scores数组里的值
gameController.avgScore= appController.avg();
函数avg()里的this将不会指代对象gameController,而会指代appController对象,因为appController对象被调用了。
要想修改这段代码,使appController.avg()里的this指代gameController,我们可以用到apply()方法:
//这里我们用到了apply(),
//所以gameController.scores必须是一个传递数据到appController.avg()方法的数组
appController.avg.apply (gameController,gameController.scores);
// avgScore属性被成功的设置到了对象gameController
//即使我们借用了对象appController中的方法
console.log (gameController.avgScore); //46.4
//appController.avgScore仍然保持空值null,它并没有被更新
//仅仅只有gameController.avgScore被更新了
console.log(appController.avgScore); // null
对象gameController借用了对象appController中的方法avg()。在方法appController.avg()里的this被设置指代对象gameController因为我们将gameController当作了apply()方法中的第一个参数,在apply()方法中第一个参数总会设置this的值
结束语:
我希望通过读完这篇文章之后,你们可以拥有足够的只是去应对JavaScript中的关键词this。一定要记住this只会被赋上“this函数”所调用的对象的值。
祝你们可以享受编程的乐趣!
译自:
http://javascriptissexy.com/understand-javascripts-this-with-clarity-and-master-it/