在本教程中,您将了解 JavaScript this 关键词并在各种上下文中清楚地理解它。

如果您一直使用其他编程语言,例如 Java、C#或PHP,那么您已经熟悉 this 关键词。在这些语言中, this 关键词表示类的当前实例。而且它仅在 Class 内使用。

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();

next() 函数内部 ,this 引用 counter 对象。

counter.next();

next() 一个函数,它是 counter 对象的一个属性。因此,在 next() 函数内部,this 引用 counter 对象。

全局上下文

在全局上下文中,this 引用全局对象,它是 Web 浏览器的 window 对象或 Node.JS 的 global 对象。

这种行为在严格和非严格模式下都是一致的。这是 Web 浏览器的输出:

console.log(this === window); // true

如果您将属性分配给 this 全局上下文中的对象,JavaScript 会将属性添加到全局对象,如以下示例所示:

this.color= 'Red';
console.log(window.color); // 'Red'

函数上下文

在 JavaScript ,您可以通过以下方式调用函数

  • 函数调用
  • 方法调用
  • 构造函数调用
  • 间接调用

每个函数调用都定义了自己的上下文。因此,this 行为会有所不同。

简单的函数调用

在非严格模式,函数调用时 this 引用全局对象,例如以下示例:

function show() {
   console.log(this === window); // true
}

show();

当您调用 show() 函数时,this 引用全局对象,这是在网络浏览器的 window 对象和Node.JS 的 global

你也可已使用以下方式调用 show() 函数:

window.show();

在严格模式下,JavaScript 将函数内部的 this 设置为 undefined. 例如:

"use strict";

function show() {
    console.log(this === undefined);
}

show();

要启用严格模式,请在 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();

输出:

true
true

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

方法调用

当您调用对象的方法时,JavaScript 设置 this 为拥有方法的对象。例如下面的 car对象:

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

console.log(car.getBrand()); // Honda

在这个例子中, this 对象在 getBrand() 方法引用 car 对象。由于方法是作为对象属性的值,因此您可以将其存储在变量。

let brand = car.getBrand;

然后通过变量调用方法

console.log(brand()); // undefined

你得到是 undefined 而不是 "Honda" 因为当你调用一个方法而不指定它的对象时,JavaScript 在非严格模式下将 this 的值设置为全局对象,在严格模式下将 this 设置为undefined

要解决此问题,您可以使用 Function.prototype 对象的 bind() 方法。bind() 方法创建一个新函数,可以将 this 设置为指定的值。

在下面例中,当您调用 brand() 方法时,this 关键字将绑定到 car 对象。例如:

let brand = car.getBrand.bind(car);
console.log(brand()); // Honda

在下面示例中,bind() 方法将 this 设置为 bike 对象,因此,您会在控制台看到 bike 对象的 brand 属性值 Harley Davidson 。

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

let bike = {
    brand: 'Harley Davidson'
}

let brand = car.getBrand.bind(bike);
console.log(brand());

输出:

Harley Davidson

构造函数调用

当您使用 new 关键词创建函数对象的实例时,您将函数用作构造函数。

以下示例声明一个 Car 函数,然后将其作为构造函数调用:

function Car(brand) {
    this.brand = brand;
}

Car.prototype.getBrand = function () {
    return this.brand;
}

let car = new Car('Honda');
console.log(car.getBrand());

表达式 new Car('Honda') 是将 Car 函数作为构造函数调用。JavaScript 创建一个新对象并设置 this 为新创建的对象。

现在,您可以调用 Car() 作为函数或构造函数。如果省略 new 关键词如下:

var bmw = Car('BMW');
console.log(bmw.brand);
// => TypeError: Cannot read property 'brand' of undefined

由于在 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;
}

ES6 引入一个名为 new.target 的元属性 ,它允许您检测一个函数是使用简单方式调用还是作为构造函数被调用。

您可以修改 Car() 函数使用 new.target 元属性,如下所示:

function Car(brand) {
    if (!new.target) {
        throw Error('Must use the new operator to call the function');
    }
    this.brand = brand;
}

间接调用

在 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 ");

输出:

It's a Honda
It's an Audi

在这个例子中,我们使用 getBrand() 函数原型对象的 call() 方法间接调用 getBrand函数。

我们将 object 作为方法的第一个参数传递,  因此,我们在每次调用中都获得了相应的品牌。call()getBrandhondaaudicall()

我们分别将 hondaaudi 对象作为 call 方法第一个参数传递,因此我们在每次调用中都能获取到正确品牌名称。

apply() 方法与 call() 方法类似,只是它的第二个参数是一个数组,数组的元素将作为 getBrand 函数的参数。

getBrand.apply(honda, ["It's a "]); // "It's a Honda"
getBrand.apply(audi, ["It's an "]); // "It's a Audi"

箭头函数

ES6 引入了一个新概念,叫做箭头函数。在箭头函数,JavaScript 按照词法设置 this

这意味着箭头函数不会创建自己的执行上下文this 的值将会被设置为定义箭头函数的外部对象。请参阅以下示例:

let getThis = () => this;
console.log(getThis() === window); // true

在此示例中,this 值设置为全局对象,即在 Web 浏览器的 window

由于箭头函数不会创建自己的执行上下文,因此使用箭头函数定义方法会导致问题。例如:

function Car() {
  this.speed = 120;
}

Car.prototype.getSpeed = () => {
  return this.speed;
};

var car = new Car();
console.log(car.getSpeed()); // 👉 undefined

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