揭秘“this”在 Javascript 中的工作原理

它一直在 Javascript 中使用,但它指的是什么通常是个谜。在 Javascript 中,它的this工作方式与其他编程语言完全不同——而且它的工作方式也不同,具体取决于您是否使用use strict模式。

如果你发现它很难,你并不孤单。让我们看看究竟是如何this工作的,并消除关于它在各种情况下的含义的任何混淆。

这是什么在 Javascript 中#

this是 Javascript 中的关键字,它指的是特定上下文中的一个属性或一组属性。我们使用的上下文this改变了它的属性。在全局上下文中,this指的是全局对象——在浏览器中是window,但globalThis在 Node.JS 和其他 Javascript 实现中是。

console.log(this); // The same as console.log(window);

在任何函数或代码之外,情况总是如此。但是,在不同的地方,this意味着不同的东西。

这在 Javascript 函数中#

在函数中,this仍然引用全局对象。如果我们this在一个函数中引用,它会默认引用window or globalThis对象:

console.log(this); // The same as console.log(window);

function myFunction() {
    console.log(this); // The same as console.log(window);
}

myFunction();

然而,在strict mode模式下this,函数内部是未定义的。

"use strict"
console.log(this); // The same as console.log(window);

function myFunction() {
    console.log(this); // This is undefined!
}

myFunction();

用 call() 解决#

起初这有点令人困惑,但这样做的原因是因为我们需要添加一个this对象myFunction——严格模式下的 Javascript 不会将其默认为全局对象。为此,我们必须使用call(). 在下面的例子中,我已经变成myObject了我们的this变量:

"use strict"
console.log(this); // The same as console.log(window);

let myObject = {
    firstName: "John",
    lastName: "Doe",
    age: 76
}
function myFunction() {
    console.log(this.firstName);
}

myFunction.call(myObject); // this.firstName is defined as "John", so it will console log John
myFunction(); // this.firstName will be undefined, and this will throw an error.

call()运行myFunction并附myObject加到this关键字。如果我们不使用调用,而只是运行myFunction(),那么该函数将返回一个错误,因为this.firstName它是未定义的。您还可以调用一个空的函数this,然后您可以将数据附加到您的函数内部。

这给了我们一个新的空间来定义我们对象的变量,而不是被来自全局对象this的数据污染:this

"use strict"
console.log(this); // The same as console.log(window);

function myFunction() {
    this.firstName = 'John';
    console.log(this.firstName); // This will be "John"
}

myFunction.call({});

严格模式下的不同行为

如您所见,行为会因我们是否使用严格模式而大不相同——因此在两种模式之间更改代码之前进行一些测试很重要。

Call and Apply

有时您可能会看到call()与称为 的函数互换使用apply()。这两个函数非常相似,因为它们都调用具有指定this上下文的函数。唯一的区别是apply(),如果函数有参数,则采用数组,而call()一个接一个地采用每个参数。

例如:

"use strict"
let otherNumbers = {
    a: 10,
    b: 4
}
function multiplyNumbers(x, y, z) {
    return this.a * this.b * x * y * z
}

// Both will return the same result, the only difference
// being that apply() uses an array for arguments.
multiplyNumbers.call(otherNumbers, 1, 2, 3);
multiplyNumbers.apply(otherNumbers, [ 1, 2, 3 ]);

使用 bind() 简化这个过程

实现类似行为的另一种方法call()是使用bind(). 与call()类似,bind(), 更改this函数的值,只是它会永久更改。这意味着您不必经常使用bind()– 您只需使用一次。

这是一个示例,我们将对象永久绑定到我们的函数,从而this永久更新 – 我们只需将其定义为一个新函数。在下面的示例中,我们定义了一个名为 的新函数boundFunction,它是我们永久绑定到它myFunction的。myObject

因此,当我们调用控制台日志时,它将显示“John”。这和call不同,call是我们每次使用一个函数都需要调用的。

"use strict"
console.log(this); // The same as console.log(window);

let myObject = {
    firstName: "John",
    lastName: "Doe",
    age: 76
}
function myFunction() {
    console.log(this.firstName);
}

let boundFunction = myFunction.bind(myObject); // this will bind this to myObject permanently.
boundFunction(); // since we used bind, this will now be set to myObject, every time we call boundFunction() - so it will return John.

箭头符号函数和这个

Javascript 中箭头符号函数的一个关键特性是它们不包含this上下文。这意味着他们this从父母那里继承。例如,假设我们处于严格模式并同时定义了箭头函数和“普通”风格的函数。对于箭头函数,this将被继承,但对于其他函数,this将保持未定义状态!

试试看!

构造函数和这个

另一个有趣的事情this是,当在构造函数(即使用new关键字的函数)中使用时,构造函数的返回本质上会覆盖this. 因此,例如,如果我们运行以下命令,虽然我们设置this.nameJohn,但返回的name值为Jack

试试看!

this在对象上下文中#

在对象上下文中,using this指的是对象。例如,假设我们在一个名为 的对象中运行一个函数obj,它指的是this.aProperty– this,在这种情况下,指的是obj

let obj = {
    aProperty: 15,
    runFunction: function() {
        console.log(this.aProperty); // Refers to 15
    }
}

obj.runFunction(); // Will console log 15, since this refers to obj

如果您使用get()/set()符号也是如此:

"use strict"
let obj = {
    aProperty: 15,
    runFunction: function() {
        console.log(this.aProperty); // Refers to 15
    },
    set updateProp(division) {
        this.aProperty = this.aProperty / division; // this.aProperty refers to 15
        console.log(this.aProperty); 
    }
}

obj.updateProp = 15; // Will divide aProperty by 15, and console log the result, i.e. 1

与事件监听器一起使用#

Javascript 的另一个怪癖this是,当使用事件侦听器时,this引用事件被添加到的 HTML 元素。在下面的示例中,我们将点击事件添加到 ID 为“hello-world”的 HTML 标记:

document.getElementById('hello-world').addEventListener('click', function(e) {
    console.log(this);
});

如果我们然后点击我们的#hello-world HTML 元素,我们将在我们的控制台日志中看到:

<div id="hello-world"></div>

与类一起使用#

在本节中值得注意的是,Javascript 中的类只是底层函数。这意味着我们在函数中看到的很多功能都适用于类。

默认情况下,类将this设置为类实例本身。在下面的例子中,我们可以看到这个动作——两者runClass.namerunClass.whatsMyName返回John

class myClass { 
    whatsMyName() {
        return this.name;
    }
    get name() {
        return "John";
    }
}

const runClass = new myClass();
console.log(runClass.name);        // Returns "John"
console.log(runClass.whatsMyName); // Returns "John"

唯一的例外是静态项不会添加到this. 所以如果我们定义一个static前面有关键字的函数,它就不会 on this

class myClass { 
    getMyAge() {
        return this.whatsMyAge();
    }
    static whatsMyAge() {
        return this.age; 
    }
    get name() {
        return "John";
    }
    get age() {
        return 143
    }
}

const runClass = new myClass();
console.log(runClass.whatsMyAge()); // Throws an error, since runClass.whatsMyAge() is undefined
console.log(runClass.getMyAge()); // Throws an error, since this.whatsMyAge() is undefined

值得注意的是,默认情况下,类始终处于严格模式 – 因此this其行为方式与类中默认情况下严格函数的行为方式相同。

结论#

在 Javascript 中,this可以表示不同的意思。在本文中,我们介绍了它在不同上下文(函数、类和对象)中的含义。我们已经介绍了如何使用bind(),call()以及为您的函数apply()添加不同的this上下文。

我们还介绍了如何this在严格模式和非严格模式下使用。在此之后,我希望this稍微揭开神秘面纱。