摘要:在本教程中,您将了解 JavaScript this
值并清楚地理解它在各种上下文中的含义。
如果您已经使用过其他编程语言,例如 Java、C# 或 PHP,您已经熟悉 this
关键字。
在这些语言中,this
关键字表示类的当前实例,它只在类内相关。
JavaScript 也拥有 this
关键字。但是,JavaScript 中的 this
关键字的行为与其他编程语言不同。
在 JavaScript 中,您可以在 全局和函数上下文 中使用 this
关键字。此外,this
关键字的行为在严格模式和非严格模式之间有所不同。
什么是 this 关键字
通常,this
引用函数所属的对象。换句话说,this
引用当前调用函数的对象。
假设您有一个对象 counter
,它有一个方法 next()
。当您调用 next()
方法时,您可以访问 this
对象。
let counter = {
count: 0,
next: function () {
return ++this.count;
},
};
counter.next();
Code language: JavaScript (javascript)
在 next()
函数内部,this
引用 counter
对象。请看以下方法调用
counter.next();
Code language: CSS (css)
next()
是一个函数,它是 counter
对象的属性。因此,在 next()
函数内部,this
引用 counter
对象。
全局上下文
在全局上下文中,this
引用 全局对象,在 Web 浏览器中是 window
对象,在 Node.js 中是 global
对象。
此行为在严格模式和非严格模式下始终如一。以下是 Web 浏览器中的输出
console.log(this === window); // true
Code language: JavaScript (javascript)
如果您在全局上下文中为 this
对象分配一个属性,JavaScript 将将该属性添加到全局对象,如以下示例所示
this.color= 'Red';
console.log(window.color); // 'Red'
Code language: JavaScript (javascript)
函数上下文
在 JavaScript 中,您可以通过以下方式调用 函数
- 函数调用
- 方法调用
- 构造函数调用
- 间接调用
每个函数调用都定义了自己的上下文。因此,this
的行为有所不同。
1) 简单函数调用
在非严格模式下,当函数以以下方式调用时,this
引用全局对象
function show() {
console.log(this === window); // true
}
show();
Code language: JavaScript (javascript)
当您调用 show()
函数时,this
引用 全局对象,在 Web 浏览器中是 window
,在 Node.js 中是 global
。
调用 show()
函数等同于
window.show();
Code language: JavaScript (javascript)
在严格模式下,JavaScript 将函数内部的 this
设置为 undefined
。例如
"use strict";
function show() {
console.log(this === undefined);
}
show();
Code language: JavaScript (javascript)
要启用严格模式,您可以在 JavaScript 文件的开头使用指令 "use strict"
。如果您想仅将严格模式应用于特定函数,则将其放置在函数体顶部。
请注意,严格模式自 ECMAScript 5.1 起开始可用。strict
模式适用于函数和嵌套函数。例如
function show() {
"use strict";
console.log(this === undefined); // true
function display() {
console.log(this === undefined); // true
}
display();
}
show();
Code language: JavaScript (javascript)
输出
true
true
Code language: JavaScript (javascript)
在 display()
内部函数中,this
也被设置为 undefined
,如控制台中所示。
2) 方法调用
当您调用对象的某个方法时,JavaScript 将 this
设置为拥有该方法的对象。请看以下 car
对象
let car = {
brand: 'Honda',
getBrand: function () {
return this.brand;
}
}
console.log(car.getBrand()); // Honda
Code language: JavaScript (javascript)
在此示例中,getBrand()
方法中的 this
对象引用 car
对象。
由于方法是对象的属性,而属性是值,因此您可以将其存储在变量中。
let brand = car.getBrand;
Code language: JavaScript (javascript)
然后通过变量调用该方法
console.log(brand()); // undefined
Code language: JavaScript (javascript)
您会得到 undefined
而不是 "Honda"
,因为当您在没有指定其对象的情况下调用方法时,JavaScript 会在非严格模式下将 this
设置为全局对象,在严格模式下将 this
设置为 undefined
。
要解决此问题,您可以使用 Function.prototype
对象的 bind()
方法。bind()
方法创建一个新函数,其 this
关键字设置为指定的值。
let brand = car.getBrand.bind(car);
console.log(brand()); // Honda
Code language: JavaScript (javascript)
在此示例中,当您调用 brand()
方法时,this
关键字被绑定到 car
对象。例如
let car = {
brand: 'Honda',
getBrand: function () {
return this.brand;
}
}
let bike = {
brand: 'Harley Davidson'
}
let brand = car.getBrand.bind(bike);
console.log(brand());
Code language: JavaScript (javascript)
输出
Harley Davidson
在此示例中,bind()
方法将 this
设置为 bike
对象,因此您在控制台上看到了 bike
对象的 brand
属性的值。
3) 构造函数调用
当您使用 new
关键字创建函数对象的实例时,您将该函数用作构造函数。
以下示例声明了 Car
函数,然后将其作为构造函数调用
function Car(brand) {
this.brand = brand;
}
Car.prototype.getBrand = function () {
return this.brand;
}
let car = new Car('Honda');
console.log(car.getBrand());
Code language: JavaScript (javascript)
表达式 new Car('Honda')
是 Car
函数的构造函数调用。
JavaScript 创建一个新对象并将 this
设置为新创建的对象。此模式效果很好,只有一个潜在问题。
现在,您可以将 Car()
作为函数或构造函数调用。如果您省略 new
关键字,如下所示
var bmw = Car('BMW');
console.log(bmw.brand);
// => TypeError: Cannot read property 'brand' of undefined
Code language: JavaScript (javascript)
由于 Car()
中的 this
值被设置为全局对象,因此 bmw.brand
返回 undefined
。
要确保始终使用构造函数调用 Car()
函数,您可以在 Car()
函数开头添加一个检查,如下所示
function Car(brand) {
if (!(this instanceof Car)) {
throw Error('Must use the new operator to call the function');
}
this.brand = brand;
}
Code language: JavaScript (javascript)
ES6 引入了一个名为 new.target
的元属性,它允许您检测函数是以简单调用方式调用还是作为构造函数调用。
您可以使用以下代码修改使用 new.target
元属性的 Car()
函数
function Car(brand) {
if (!new.target) {
throw Error('Must use the new operator to call the function');
}
this.brand = brand;
}
Code language: JavaScript (javascript)
4) 间接调用
在 JavaScript 中,函数是一等公民。换句话说,函数是对象,它们是 Function 类型 的实例。
Function
类型有两个方法:call()
和 apply()
。这些方法允许您在调用函数时设置 this
值。例如
function getBrand(prefix) {
console.log(prefix + this.brand);
}
let honda = {
brand: 'Honda'
};
let audi = {
brand: 'Audi'
};
getBrand.call(honda, "It's a ");
getBrand.call(audi, "It's an ");
Code language: JavaScript (javascript)
输出
It's a Honda
It's an Audi
Code language: PHP (php)
在此示例中,我们使用 getBrand
函数的 call()
方法间接调用了 getBrand()
函数。我们将 honda
和 audi
对象作为 call()
方法的第一个参数传递,因此我们在每次调用中都获得了相应的品牌。
apply()
方法与 call()
方法类似,除了它的第二个参数是参数数组。
getBrand.apply(honda, ["It's a "]); // "It's a Honda"
getBrand.apply(audi, ["It's an "]); // "It's a Audi"
Code language: JavaScript (javascript)
箭头函数
ES6 引入了名为 箭头函数 的新概念。在箭头函数中,JavaScript 会词法地设置 this
。
这意味着箭头函数不会创建自己的 执行上下文,而是继承定义箭头函数的外部函数的 this
。请看以下示例
let getThis = () => this;
console.log(getThis() === window); // true
Code language: JavaScript (javascript)
在此示例中,this
值被设置为全局对象,即 Web 浏览器中的 window
。
由于箭头函数不会创建自己的执行上下文,因此使用箭头函数定义方法会导致问题。例如
function Car() {
this.speed = 120;
}
Car.prototype.getSpeed = () => {
return this.speed;
};
var car = new Car();
console.log(car.getSpeed()); // 👉 undefined
Code language: JavaScript (javascript)
在 getSpeed()
方法内部,this
值引用全局对象,而不是 Car
对象,但全局对象没有名为 speed 的属性。因此,getSpeed()
方法中的 this.speed
返回 undefined
。