揭秘 JavaScript 的 this 关键字

摘要:在本教程中,您将了解 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); // trueCode 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
trueCode language: JavaScript (javascript)

display() 内部函数中,this 也被设置为 undefined,如控制台中所示。

2) 方法调用

当您调用对象的某个方法时,JavaScript 将 this 设置为拥有该方法的对象。请看以下 car 对象

let car = {
    brand: 'Honda',
    getBrand: function () {
        return this.brand;
    }
}

console.log(car.getBrand()); // HondaCode language: JavaScript (javascript)

在此示例中,getBrand() 方法中的 this 对象引用 car 对象。

由于方法是对象的属性,而属性是值,因此您可以将其存储在变量中。

let brand = car.getBrand;Code language: JavaScript (javascript)

然后通过变量调用该方法

console.log(brand()); // undefinedCode 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 undefinedCode 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 AudiCode language: PHP (php)

在此示例中,我们使用 getBrand 函数的 call() 方法间接调用了 getBrand() 函数。我们将 hondaaudi 对象作为 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); // trueCode 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()); // 👉 undefinedCode language: JavaScript (javascript)

getSpeed() 方法内部,this 值引用全局对象,而不是 Car 对象,但全局对象没有名为 speed 的属性。因此,getSpeed() 方法中的 this.speed 返回 undefined

本教程对您有帮助吗?