本文深入解析 JavaScript 中 this 关键字的指向逻辑,涵盖全局作用域、对象方法调用、new 构造函数以及 call、apply、bind 等显式绑定方式。通过具体代码示例,帮助开发者理解不同执行环境下 this 的绑定规则,避免常见陷阱。
JavaScript this 关键字概述
JavaScript 中的 this 关键字与 Java 等面向对象语言中的实现存在显著差异,而 bind()、call() 和 apply() 方法进一步增强了 this 绑定的灵活性。
出于可读性考虑,本文采用意译方式而非逐字直译。文章版权归原作者所有,翻译内容仅供学习参考。
若对 JavaScript 中的 this 理解不够深入,容易陷入意想不到的逻辑陷阱。本文总结了五条通用规则,用于判断 this 的具体指向。虽然这些规则未涵盖所有极端情况,但足以应对日常开发中的大部分场景。
this 的值通常由函数的执行环境决定,即取决于函数是如何被调用的。这意味着同一个函数在每次调用时,this 可能指向不同的对象。
全局作用域中的 this
全局对象 (Global Object)
打开 Chrome 浏览器开发者面板(Windows 系统按 Ctrl + Shift + J,Mac 系统按 Cmd + Option + J),并在控制台中输入以下代码:
console.log(this);
观察控制台输出结果。
// Window {}
输出结果为 window 对象。这是因为在全局作用域下,this 默认指向全局对象,在浏览器环境中即为 window 对象。为了更清晰地理解这一机制,我们来看另一个示例:
var myName = 'Brandon';
你可以在控制台输入 myName 来访问其值:
myName
// 输出 'Brandon'
事实上,所有在全局作用域中定义的变量都会自动绑定到 window 对象。我们可以进行如下测试:
window.myName
// 输出 'Brandon'
window.myName === myName
// 输出 true
接下来,我们将 this 放入函数内部,观察其表现。
function test(){
return this;
}
test();
你会发现 this 依然指向全局的 window 对象。这是因为 this 关键字并未处于某个声明对象的内部,因此默认指向全局 window 对象。这一特性对许多初学者来说可能难以理解,但读完本文后你会豁然开朗。 注意:如果在 strict 模式下运行上述代码,this 的值将为 undefined。
对象方法中的 this
声明的对象 (Declared Object)
当 this 关键字在声明的对象内部使用时,其值会被绑定到调用该函数的最近父对象。我们通过示例来说明这一机制:
var person = {
first: 'John',
last: 'Smith',
full: function() {
console.log(this.first + ' ' + this.last);
}
};
person.full();
// 输出 'John Smith'
在声明的对象 person 中的 full 函数内部使用了 this,因此调用 this 的 full 函数的最近父对象是 person,所以 this 指向 person。
为了更直观地展示 this 实际上指向 person 对象,你可以将以下代码复制到浏览器控制台中,打印出 this 的值。
var person = {
first: 'John',
last: 'Smith',
full: function() {
console.log(this);
}
};
person.full();
// 输出 Object {first: "John", last: "Smith", full: function}
接下来我们看一个更复杂的嵌套对象示例:
var person = {
first: 'John',
last: 'Smith',
full: function() {
console.log(this.first + ' ' + this.last);
},
personTwo: {
first: 'Allison',
last: 'Jones',
full: function() {
console.log(this.first + ' ' + this.last);
}
}
};
在此例中,this 分别指向哪里?我们将其打印出来验证:
person.full();
// 输出 'John Smith'
person.personTwo.full();
// 输出 'Allison Jones'
结果符合前文所述的规则:this 的值会被绑定到调用 this 的函数的最近父对象。
使用 new 关键字时的 this
new 关键字
当使用 new 关键字构建新对象时,this 会绑定到这个新创建的对象。我们来看一个示例:
function Car(make, model) {
this.make = make;
this.model = model;
};
根据第一条规则,你可能会推断 this 指向全局对象。但是,如果我们使用 new 关键字声明一个新变量,Car 函数中的 this 将绑定到一个新的空对象,并初始化 this.make 和 this.model 的值。
var myCar = new Car('Ford', 'Escape');
console.log(myCar);
// 输出 Car {make: "Ford", model: "Escape"}
显式绑定:call、apply 和 bind
call, bind, 和 apply
我们可以显式地在 call()、bind() 和 apply() 中设置 this 的绑定对象。这三个函数功能相似,但需要注意它们之间的细微区别。
<!-- call 可以接收任意个数的参数,其中第一个必须是一个 this 对象,其余依次是所有的参数。 -->
我们来看一个示例:
function add(c, d) {
console.log(this.a + this.b + c + d);
}
add(3,4);
// 输出 NaN
add 函数输出 NaN,因为 this.a 和 this.b 未定义。
现在我们引入对象,并使用 call() 和 apply() 来调用:
function add(c, d) {
console.log(this.a + this.b + c + d);
}
var ten = {a: 1, b: 2};
add.call(ten, 3, 4);
// 输出 10
add.apply(ten, [3,4]);
// 输出 10
当我们使用 add.call() 时,第一个参数是 this 需要绑定的对象,其余参数是 add 函数原本的参数。 因此,this.a 指向 ten.a,this.b 指向 ten.b。add.apply() 的情况类似,唯一的区别是第二个参数是一个数组,用于存储 add 函数的参数。
bind() 函数与 call() 类似,但 bind() 不会立即执行。bind() 会返回一个新函数,并将 this 绑定好。接下来我们通过示例来理解 bind() 的应用场景:
var small = {
a: 1,
go: function(b,c,d){
console.log(this.a+b+c+d);
}
}
var large = {
a: 100
}
执行以下代码:
small.go(2, 3, 4);
// 输出 10
如果我们想使用 large.a 的值,而不是 small.a 呢?我们可以使用 call/apply:
small.go.call(large, 2, 3, 4);
// 输出 109
但是,如果我们现在还不知道这三个参数应该传入什么值,应该怎么办呢?我们可以使用 bind:
var bindTest = small.go.bind(large, 2);
如果我们将 bindTest 在控制台下打印出来,我们会看到:
console.log(bindTest);
// 输出 function (b,c,d){console.log(this.a+b+c+d);}
注意:该函数已经将 this 绑定到 large 对象,并且传入了第一个参数 b。因此,接下来我们只需要传入剩余的参数即可:
bindTest(3, 4);
// 输出 109
箭头函数与 this
箭头函数(=>)
由于篇幅较大,我们将专门撰写一篇博客来详细介绍。
总结
结论
读完本文后,你应该能够理解大多数情况下 this 指向的对象。 接下来我们来总结一下:
this 的值通常由当前函数的执行环境决定; 在全局作用域中,this 指向全局对象(window 对象); 当使用 new 关键字声明时,this 指向新建对象; 我们可以使用 call()、bind()、apply() 来设置 this; 箭头函数不会绑定 this。
原文: JavaScript: The Keyword ‘This' for Beginners
译者: Fundebug