JavaScript:this关键词5种方法绑定规则

2021-04-04

cover_freecodecamp

JavaScript中this关键词5种方法绑定规则

JavaScript的this关键字是该语言最难掌握的方面之一。但是对于编写更高级的JavaScript代码至关重要。

在JavaScript中,this关键字使我们能够:

  • 在不同的执行上下文中重用函数。这意味着,一个函数定义一次,可以使用this关键字为不同的对象调用。
  • 调用方法时,在当前执行上下文中标识对象。

this关键字与JavaScript函数紧密相关。当涉及到这一点时,基本的事情就是了解在何处调用函数。因为直到调用该函数,我们才知道this关键字中的内容。 它的用法可以分为5个不同方面绑定。在本文中,我们将通过示例全面了解这5个方面。

首先,什么是绑定?

在JavaScript中,词法环境是物理编写代码的地方。在下面的示例中,变量名按词法位于函数sayName()中。

1
2
3
4
function sayName() {
let name = 'someName';
console.log('The name is, ', name);
}

执行上下文是指当前正在运行的代码以及有助于运行它的所有其他内容。可能有许多词汇环境可用,但是当前正在运行的词汇环境由执行上下文管理。

lexical

每个执行上下文都包含一个环境记录。当JavaScript引擎执行代码时,变量和函数名将添加到环境记录中。

这种现象在JavaScript中称为Binding。绑定有助于将标识符(变量,函数名称)与this关键字关联用于执行上下文

如果您现在觉得有点难以理解,请不要担心。随着我们的进行,您将更好地掌握。

规则1:JavaScript隐式绑定的工作方式

隐式绑定涵盖了处理this关键字的大多数用例。

在隐式绑定中,您需要在调用时检查与函数相邻的dot(.)运算符左侧的内容。这确定了要绑定的内容。

让我们看一个例子,以更好地理解它。

1
2
3
4
5
6
7
8
9
let user = {
name: 'Tapas',
address: 'freecodecamp',
getName: function() {
console.log(this.name);
}
};

user.getName();

在这里,这绑定到user对象。我们知道这一点是因为在函数getName()旁边的dot(.)运算符的左侧,我们看到了用户对象。因此,this.name将在控制台中打印Tapas

让我们看另一个例子,以更好地理解这个概念:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function decorateLogName(obj) {
obj.logName = function() {
console.log(this.name);
}
};

let tom = {
name: 'Tom',
age: 7
};

let jerry = {
name: 'jerry',
age: 3
};

decorateLogName(tom);
decorateLogName(jerry);

tom.logName();
jerry.logName();

在此示例中,我们有两个对象,tomjerry。我们通过附加一个名为logName()的方法来修饰(增强)这些对象。 请注意,当我们调用tom.logName()时,tom对象位于与函数logName()相邻的dot(.)运算符的左侧。因此,这绑定到了tom对象,并记录了tom值(此名称在此处等于tom)。调用jerry.logName()时,也是如此。

规则2:JavaScript显式绑定的工作方式

我们已经看到JavaScript创建了执行我们编写的代码的环境。它负责在创建阶段为变量,函数,对象等创建内存。最后,它在执行阶段执行代码。这种特殊的环境称为执行上下文

JavaScript应用程序中可以有许多这样的环境(执行上下文)。每个执行上下文都独立于其他上下文运行。

但是有时,我们可能想在另一个执行上下文中使用东西。这就是显式绑定起作用的地方。

在显式绑定中,当函数在对象的执行上下文之外时,我们可以使用该对象来调用函数。

有三种非常特殊的方法,分别是call()apply()bind(),它们可以帮助我们实现显式绑定。

JavaScript中 的call()方法的工作方式

使用call()方法,必须将调用函数的上下文作为参数传递给call()。让我们用一个例子看看它是如何工作的:

1
2
3
4
5
6
7
8
9
10
let getName = function() {
console.log(this.name);
}

let user = {
name: 'Tapas',
address: 'Freecodecamp'
};

getName.call(user);

在这里,call()方法是在名为getName()的函数上调用的。 getName()函数仅仅记录this.name。但是,这是什么?这取决于传递给call()方法的内容。 在这里,这将绑定到user对象,因为我们已经将user作为参数传递给call()方法。因此,this.name应该记录user对象(即Tapas)的name属性的值。

在上面的示例中,我们仅将一个参数传递给call()。但是我们也可以将多个参数传递给call(),如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
let getName = function(hobby1, hobby2) {
console.log(this.name + ' likes ' + hobby1 + ' , ' + hobby2);
}

let user = {
name: 'Tapas',
address: 'Bangalore'
};

let hobbies = ['Swimming', 'Blogging'];

getName.call(user, hobbies[0], hobbies[1]);

在这里,我们已经将多个参数传递给call()方法。第一个参数必须是必须使用该函数调用的对象上下文。其他参数可能只是要使用的值。

在这里,我将SwimmingBlogging作为两个参数传递给getName()函数。

您在这里注意到痛点了吗?如果是call(),则必须逐个传递参数-这不是一种明智的处理方式!这就是我们的下一个方法apply()出现在其中的地方。

JavaScript中的 apply()方法的工作方式

这种将参数传递给call()方法的方法很忙,可以通过另一个名为apply()的替代方法来解决。它与call()完全相同,但允许您更方便地传递参数。看一看:

1
2
3
4
5
6
7
8
9
10
11
12
let getName = function(hobby1, hobby2) {
console.log(this.name + ' likes ' + hobby1 + ' , ' + hobby2);
}

let user = {
name: 'Tapas',
address: 'Bangalore'
};

let hobbies = ['Swimming', 'Blogging'];

getName.apply(user, hobbies);

在这里,我们能够传递一个参数数组,这比一个一个地传递参数要方便得多。

提示:当您仅要传递一个值参数或没有值参数时,请使用call()。当您有多个要传递的值参数时,请使用apply()

JavaScript中的 bind()方法的工作方式

bind()方法类似于call()方法,但有一个区别。与直接调用该函数的call()方法不同,bind()返回一个全新的函数,而我们可以调用它。

1
2
3
4
5
6
7
8
9
10
11
12
13
let getName = function(hobby1, hobby2) {
console.log(this.name + ' likes ' + hobby1 + ' , ' + hobby2);
}

let user = {
name: 'Tapas',
address: 'Bangalore'
};

let hobbies = ['Swimming', 'Blogging'];
let newFn = getName.bind(user, hobbies[0], hobbies[1]);

newFn();

在这里,getName.bind()不会直接调用函数getName()。它返回一个新函数newFn,我们可以将其作为newFn()调用。

规则3:JavaScript中的new绑定

new关键字用于根据构造函数创建对象。

1
2
3
4
5
6
7
let Cartoon = function(name, character) {
this.name = name;
this.character = character;
this.log = function() {
console.log(this.name + ' is a ' + this.character);
}
};

您可以使用new关键字创建对象,如下所示:

1
2
let tom = new Cartoon('Tom', 'Cat');
let jerry = new Cartoon('Jerry', 'Mouse');

当使用new关键字调用函数时,JavaScript在函数内创建一个内部this对象(例如this = {})。新创建的this绑定到使用new关键字创建的对象。

听起来复杂吗?好吧,让我们分解一下。看看这一行,

1
let tom = new Cartoon('Tom', 'Cat');

在这里,函数Cartoon是通过new关键字调用的。因此,内部创建的对象将绑定到此处要创建的新对象,即tom

来自于网络,构造函数this的理解,

image-20210404125407947

规则4:JavaScript全局对象绑定

您认为以下代码的输出是什么?这对这里有什么约束?

1
2
3
4
5
6
let sayName = function(name) {
console.log(this.name);
};

window.name = 'Tapas';
sayName();

如果this关键字不使用任何隐式显式new绑定解析,则this绑定到window(global)对象。

但是有一个例外。 JavaScript严格模式不允许此默认绑定。

1
2
3
4
"use strict";
function myFunction() {
return this;
}

在上述情况下,thisundefined

规则5:JavaScript中的HTML事件元素绑定

在HTML事件处理程序中,this绑定到接收事件的HTML元素。

1
<button onclick="console.log(this)">Click Me!</button>

当您单击按钮时,这是控制台中的输出日志:

1
"<button onclick='console.log(this)'>Click Me!</button>"

您可以使用this关键字更改按钮样式,如下所示:

1
<button onclick="this.style.color='teal'">Click Me!</button>

但是请注意,在单击按钮时调用某个函数并在该函数内部使用this

1
<button onclick="changeColor()">Click Me!</button>

和JavaScript代码:

1
2
3
function changeColor() {
this.style.color='teal';
}

上面的代码无法正常工作。正如我们在规则4中所看到的,这里this将绑定到没有样式对象来设置颜色的全局对象(在“非严格”模式下)。

总结

  • 对于隐式绑定,this将绑定到dot(.)运算符左侧的对象。
  • 在显式绑定的情况下,当函数位于对象的执行上下文之外时,我们可以使用该对象来调用函数。方法call()apply()bind()在这里起着重要作用。
  • 使用new关键字调用函数时,函数内的this关键字绑定到正在构造的新对象。
  • this关键字不使用任何隐式显式new绑定解析时,则将其绑定到window(global)对象。在JavaScript的严格模式下,thisundefined的。
  • 在HTML事件处理程序中,this绑定到接收事件的HTML元素

在另外一种情况下,this的行为也有所不同,例如使用ES6箭头函数。我们将在以后的文章中对此进行介绍。

希望您对本文有所了解。你也许也喜欢,

后记

以上翻译自The JavaScript this Keyword + 5 Key Binding Rules Explained for JS Beginners