彻底搞懂JavaScript中的this关键字:原理与实战技巧
本文深入解析JavaScript中this关键字的核心机制,指出其指向取决于调用方式而非定义位置。详细剖析全局作用域、回调函数、闭包及方法借用四种常见场景下的this指向问题,并提供bind、apply、call及变量保存等实用解决方案,帮助开发者彻底消除困惑。
this关键字概述与基本理论
JavaScript中的this关键字常令新老开发者感到困惑。本文将系统阐述this的工作原理,帮助读者彻底消除疑惑,掌握在各种场景下正确运用this的技巧。
在自然语言如英语或法语中,我们像使用名词一样使用代词。例如,“John飞快地跑着,因为他想追上火车”,这里的“他”指代John。虽然可以说“John飞快地跑着,因为John想追上火车”,但日常交流中我们避免这种冗余表达,否则会被视为怪异。同理,在JavaScript中,this作为引用(referent)的快捷方式,指向当前上下文(context)的主体,即正在执行的代码块的所属对象。
考虑以下代码:
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可能意外从全局变量中获取属性,导致调试困难。因此,this不仅美化代码,更保证程序准确性。这与前文“他”指代“John”的逻辑一致,使代码意图更清晰。
正如代名词“他”指代句中的先行词,this关键字引用当前方法(function)所绑定的对象。this不仅引用对象,还包含其值。如同先行词,this可视为上下文中引用当前对象(即“先行词对象”)的快捷方式,旨在减少歧义。下文将专门讲解“上下文”概念。
this关键字基本理论
首先需知,对象(Object)拥有属性集(properties),所有方法(function)也有属性集。当执行某个方法时,会产生一个this属性——这是一个存储了调用该方法(准确说是使用了this关键字的方法)的对象值的变量。
this始终指向一个对象并持有其值,尽管它可出现在全局范围(global scope)或方法(函数)之外,但通常出现在方法体中。值得注意的是,若在严格模式(strict mode)下,于全局方法或未绑定到任何对象的匿名方法中使用this,其值将为undefined。
当this用于方法体(如方法A)时,它指向调用方法A的对象的值。并非总能找到调用方法A的对象名,此时便用this访问该对象拥有的方法和属性。this确实只是引用先行词——即调用方法的对象——的快捷方式。
我们来仔细体会下面这段使用this的代码。
var person = {
firstName: "Penelope",
lastName: "Barrymore",
//this用在showFullName方法中,而showFullName定义在person对象中,由于调用showFullName的是person这个对象,所以this拥有person的值
showFullName: function() {
console.log(this.firstName + " " + this.lastName);
}
}
person.showFullName(); // 结果:Penelope Barrymore
再考虑下面这段使用了this的jQuery示例。
//这是一段很简单的jQuery代码
$("button").click(function(event) {
// $(this) 会指向$("button")对象
// 因为$("button")对象调用click方法
console.log($(this).prop("name"));
});
详细解析上述jQuery示例中$(this)的使用:这是this在jQuery中的表现形式,用于button单击事件内的匿名方法。之所以说$(this)绑定到button对象,是因为jQuery库将$(this)绑定到调用click方法的对象上。因此,尽管$(this)定义在无法直接访问“自身”变量的匿名方法中,它仍指向button对象。
请注意,button是HTML页面的DOM元素,同时也是一个对象:在上述例子中,因其被包裹在jQuery的$()方法中,故成为一个jQuery对象。
this关键字的核心机制
this关键字的核心
以下规则有助于彻底理解this:若方法内部使用了this关键字,仅当对象调用该方法时,this才会被赋值。我们将使用了this的方法暂称为“this方法”。
尽管看似this引用了代码中存在的对象,但在方法被调用前它并未被赋值,且赋给它的值严格依赖于实际调用“this方法”的对象。this通常被赋予调用对象的值,但存在一些特殊情况。
全局范围中的this指向
全局范围里的this
在全局域中,代码在浏览器里执行,所有变量和方法均属于window对象。因此,当在全局域中使用this关键字时,它指向(并拥有)全局变量window对象。如前所述,严格模式除外。window对象是JavaScript程序或网页的主容器。
因而:
var firstName = "Peter",
lastName = "Ally";
function showFullName() {
//在这个方法中,this将指向window对象。因为showFullName()出现在全局域里。
console.log(this.firstName + " " + this.lastName);
}
var person = {
firstName: "Penelope",
lastName: "Barrymore",
showFullName: function() {
//下面这行代码,this指向person对象,因为showFullName方法会被person对象调用。
console.log(this.firstName + " " + this.lastName);
}
}
showFullName(); // Peter Ally
//window对象包含了所有的全局变量和方法,因而会有以下输出
window.showFullName(); // Peter Ally
//使用了this关键字的showFullName方法定义在person对象里,this关键字指向person对象,因以会有以下输出
person.showFullName(); // Penelope Barrymore
应对this的常见场景与解决方案
对付this有绝招
当方法内使用了this关键字时,以下几种情况最易引起误解:方法被借用;方法被赋值给变量;方法作为回调函数(callback)传递;this所在的方法是闭包(内部方法)。我们将逐一解决这些问题。在此之前,先简要介绍“上下文”(context)。
JavaScript中的上下文类似于句子中的主语(subject):“John是赢家,他还了钱”。这句话的主语是John,也可说上下文是John,因为关注点在于John,即使有“他”字代指。正如可用分号切换句子主语,通过不同对象调用方法,当前上下文对象亦可被切换。
类似地,以下JavaScript代码:
var person = {
firstName: "Penelope",
lastName: "Barrymore",
showFullName: function() {
// 上下文
console.log(this.firstName + " " + this.lastName);
}
}
//使用person对象调用showFullName的时候,上下文是person对象
//showFullName内部的this指向person对象
person.showFullName(); // Penelope Barrymore
//如果我们用不同的对象来调用showFullName
var anotherPerson = {
firstName: "Rohit",
lastName: "Khan"
};
//我们可以使用apply方法来显式设置this的值—迟些我们会讲到apply方法
//this会指向任何一个调用了this方法的对象,因此会有以下输出结果
person.showFullName.apply(anotherPerson); // Rohit Khan
//所以现在的上下文是anotherPerson,因为anotherPerson通过借助使用apply方法间接调用了person的showFullName方法
现在开始正式讨论应对this关键字的绝招,示例包含this引发的错误及解决方案。
回调函数中的this指向问题
1.当this被用作回调函数传入其它方法
当把使用this的方法作为参数传入回调函数时,麻烦随之而来。例如:
//以下是一个简单的对象,我们定义了一个clickHandler方法。我们想让这个方法在页面上某个button被单击时执行。
var user = {
data: [{
name: "T. Woods",
age: 37
},
{
name: "P. Mickelson",
age: 43
}],
clickHandler: function(event) {
var randomNum = ((Math.random() * 2 | 0) + 1) - 1; // 随机返回0或1
//下面这行代码会从数组data里随机打印姓名和年龄
console.log(this.data[randomNum].name + " " + this.data[randomNum].age);
}
}
//button对象被jQuery的$方法包装,现在变成一个jQuery对象
//所以输出结果是undefined,因为button对象没有data这个属性
$("button").click(user.clickHandler); // 无法读取未定义的属性
上述代码中,我们将user.clickHandler作为回调函数传入$(“button”)对象的click事件,user.clickHandler中的this将不再指向user对象。谁调用了包含this的方法,this就指向谁。真正调用user.clickHandler的是button对象——user.clickHandler在button对象的单击方法里执行。
注意,尽管我们使用user.clickHandler来调用clickHandler方法(因clickHandler定义在user对象上),但clickHandler方法本身会被当前this所指向的上下文对象调用。因此,此时this指向的是$(“button”)对象。
当上下文改变时——即我们在其他对象而非原对象上执行方法时,this关键字不再指向定义this的原对象。
解决方案:
由于我们确实希望this.data指向user对象的data属性,可使用Bind/Apply/Call等方法强制改变this指向的对象。本系列其他篇章将专门讲解Bind/Apply/Call,介绍不同情况下强制改变this值的方法。与其在此长篇大论,强烈建议直接阅读相关篇目(译者注:后续放出)。
为解决前述代码问题,可使用bind方法。
针对下面这行代码:
$ ("button").click (user.clickHandler); 我们可以用bind方法把clickHandler绑定的user对象上:
闭包中的this访问限制
$("button").click (user.clickHandler.bind (user)); // P. Mickelson 43 2.闭包中的this
在内部方法或闭包中使用this,是另一个易被误解的例子。必须注意,内部方法不能直接通过使用this关键字访问外部方法的this变量,因为this变量只能被特定方法本身使用。例如:
var user = {
tournament: "The Masters",
data: [{
name: "T. Woods",
age: 37
},
{
name: "P. Mickelson",
age: 43
}],
clickHandler: function() {
//在里用this.data没有太大问题,因为this指向的是user对象,data是user的一个属性
this.data.forEach(function(person) {
//但是在这个匿名方法(作为参数被传进forEach方法的这个方法)里,this不再指向user对象
//内部方法无法访问外部方法的this
console.log("What is This referring to? " + this); //输出结果为:[object Window]
console.log(person.name + " is playing at " + this.tournament);
// T. Woods is playing at undefined
// P. Mickelson is playing at undefined
})
}
}
user.clickHandler(); // What is "this" referring to? [object Window]
因匿名方法中的this无法访问外部方法的this,在非严格模式下,this指向全局的window对象。
解决方案:
在进入forEach方法前,额外使用一个变量来引用this。
var user = {
tournament: "The Masters",
data: [{
name: "T. Woods",
age: 37
},
{
name: "P. Mickelson",
age: 43
}],
clickHandler: function(event) {
//为了捕获this指向user对象时的值,我们把它赋值给另外一个变量theUserObj,后面我们可以使用theUserObj
var theUserObj = this;
this.data.forEach(function(person) {
//现在我们不用this.tournament了,我们用theUserObj.tournament
console.log(person.name + " is playing at " + theUserObj.tournament);
})
}
}
user.clickHandler();
// T. Woods is playing at The Masters
// P. Mickelson is playing at The Masters
如下代码所示,许多JavaScript开发人员喜欢使用变量that来设置this的值。但我个人不太喜欢that这个名字,更倾向使用能一眼看出this指向的名称,故上述代码中使用theUserObj = this。
// 这句代码对大多数JS开发人员来说再常见不过了
var that = this;
方法赋值与借用方法的this处理
3.方法被赋值给某个变量
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) {
var randomNum = ((Math.random() * 2 | 0) + 1) - 1; // 随机生成1或0
//这句话会从数组data里随机显示人名和岁数
console.log(this.data[randomNum].name + " " + this.data[randomNum].age);
}
}
// 把user.showData方法赋值给变量 showUserData
var showUserData = user.showData;
//执行showUserData方法,结果将 来自全局的data数组而非user对象的data属性
showUserData(); // Samantha 12 (来自全局变量data)
//解决方案:通过使用bind方法来显式设置this的值
//把showData方法绑定到user对象上
var showUserData = user.showData.bind(user);
//现在结果将来自user对象,因为this关键字已经被强制绑定到user对象上了
showUserData(); // P. Mickelson 43
4.借用方法带来的问题
JavaScript开发中,借用方法(borrowing methods)很常见。
我们来看下面的代码:
//下面代码中有两个对象。其中一个定义了avg方法,另一个不包含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() {
var sumOfScores = this.scores.reduce(function(prev, cur, index, array) {
return prev + cur;
});
this.avgScore = sumOfScores / this.scores.length;
}
}
//如果执行下面的代码,gameController.avgScore属性的实际取值将由appController的scores而来
//不要执行下面的代码,我们只是为了对这种情况进行说明。实际上我们想让appController.avgScore仍然为null。
gameController.avgScore = appController.avg();
avg方法的this关键字指向gameController对象,若使用appController调用该方法,this将指向appController(但这并非期望结果,因为我们仅想借用方法逻辑而非具体数据来源)。
解决方案:
为保证gameController只借用appController的avg方法逻辑,使用apply方法:
// 我们要使用apply方法,注意这里传入appController.avg方法的第二个参数
appController.avg.apply(gameController, gameController.scores);
//尽管avg方法是借来的,但是现在avgScore属性已经被成功地应用到gameController上了。
console.log(gameController.avgScore); // 46.4
//appController.avgScore仍然是null,只有gameController的avgScore被更新了
console.log(appController.avgScore); // null
gameController只借用appController的avg方法,此时this将指向gameController,因为我们将gameController作为apply方法的第一个参数传递。apply方法的第一个参数将显式设置this的取值。
结语
希望本文对您有所启发。现在,您可以使用文中介绍的绝招(bind方法、apply方法、call方法以及将this赋值给变量)来应对与this相关的所有问题。
正如已了解到的,在上下文改变、作为回调函数使用、被不同对象调用或方法被借用的情况下,this始终指向调用当前方法的对象。
JavaScript闭包与类(Class)的区别及适用场景对比
2026-11-05 15:48:57
JavaScript入门要学ES6吗?let与箭头函数等新特性解析
2026-11-05 14:49:16
JavaScript 8种数据类型详解:基本类型与引用类型的区别
2026-11-05 14:25:15
怎么在JavaScript中使用async/await优化Ajax异步流程
2026-11-05 13:56:15
JavaScript replace回调函数实现动态正则替换逻辑
2026-11-05 13:52:13
