如何在JavaScript中理解关键字this和context

by Lukas Gisder-Dubé

卢卡斯·吉斯杜比(LukasGisder-Dubé)

如何在JavaScript中理解关键字this和context (How to understand the keyword this and context in JavaScript)

As mentioned in one of my earlier articles, mastering JavaScript fully can be a lengthy journey. You may have come across this on your journey as a JavaScript Developer. When I started out, I first saw it when using eventListeners and with jQuery. Later on, I had to use it often with React and I am sure you also did. That does not mean that I really understood what it is and how to fully take control of it.

正如我之前的一篇文章中提到的那样 ,完全掌握JavaScript可能是一段漫长的旅程。 作为JavaScript开发人员,您可能在旅途中遇到了this问题。 当我开始使用jQuery和eventListeners时,我第一次看到它。 后来,我不得不经常将它与React一起使用,并且我相信您也这样做了。 这并不意味着我真的了解了它是什么以及如何完全控制它。

However, it is very useful to master the concept behind it, and when approached with a clear mind, it is not very difficult either.

但是,掌握其背后的概念非常有用,而且当头脑清晰时,也不是很困难。

挖这个 (Digging into this)

Explaining this can lead to a lot of confusion, simply by the naming of the keyword.

仅仅通过关键字的命名来解释this会导致很多混乱。

this is tightly coupled to what context you are in, in your program. Let’s start all the way at the top. In our browser, if you just type this in the console, you will get the window-object, the outermost context for your JavaScript. In Node.js, if we do:

this与程序中所处的上下文紧密相关。 让我们从头开始。 在我们的浏览器中,如果仅在控制台中键入this ,则将获得window -object,即JavaScript的最外层上下文。 在Node.js中,如果这样做:

console.log(this)

we end up with {}, an empty object. This is a bit weird, but it seems like Node.js behaves that way. If you do

我们最后得到一个空对象{} 。 这有点怪异,但似乎Node.js的行为是这样的。 如果你这样做

(function() {
  console.log(this);
})();

however, you will receive the global object, the outermost context. In that context setTimeout , setInterval , are stored. Feel free to play around a little bit with it to see what you can do with it. As from here, there is almost no difference between Node.js and the browser. I will be using window. Just remember that in Node.js it will be the global object, but it does not really make a difference.

但是,您将收到global对象,即最外部的上下文。 在该上下文中,存储setTimeoutsetInterval 。 随意尝试一下它,看看您可以使用它做什么。 从这里开始,Node.js和浏览器之间几乎没有区别。 我将使用window 。 请记住,在Node.js中它将是global对象,但实际上并没有什么不同。

Remember: Context only makes sense inside of functions

切记:上下文仅在函数内部有意义

Imagine you write a program without nesting anything in functions. You would simply write one line after another, without going down specific structures. That means you do not have to keep track of where you are. You are always on the same level.

假设您在编写程序时没有在函数中嵌套任何内容。 您只需要逐行写一行,而无需了解特定的结构。 这意味着您不必跟踪自己的位置。 您始终处于同一级别。

When you start having functions, you might have different levels of your program and this represents where you are, what object called the function.

当您开始拥有函数时,您可能具有不同级别的程序,并且this表示您在哪里,该对象称为函数。

跟踪调用者对象 (Keeping track of the caller object)

Let’s have a look at the following example and see how this changes depending on the context:

让我们来看看下面的例子,看看this取决于环境的变化:

const coffee = {
  strong: true,
  info: function() {
    console.log(`The coffee is ${this.strong ? '' : 'not '}strong`)
  },
}

coffee.info() // The coffee is strong

Since we call a function that is declared inside the coffee object, our context changes to exactly that object. We can now access all of the properties of that object through this . In our example above, we could also just reference it directly by doing coffee.strong . It gets more interesting, when we do not know what context, what object, we are in or when things simply get a bit more complex. Have a look at the following example:

由于我们调用了在coffee对象内部声明的函数,因此我们的上下文将完全更改为该对象。 现在,我们可以通过this访问该对象的所有属性。 在上面的示例中,我们还可以通过执行coffee.strong直接引用它。 当我们不知道我们所处的上下文,对象是什么或事情变得更加复杂时,它会变得更加有趣。 看下面的例子:

const drinks = [
  {
    name: 'Coffee',
    addictive: true,
    info: function() {
      console.log(`${this.name} is ${this.addictive ? '' : 'not '} addictive.`)
    },
  },
  {
    name: 'Celery Juice',
    addictive: false,
    info: function() {
      console.log(`${this.name} is ${this.addictive ? '' : 'not '} addictive.`)
    },
  },
]

function pickRandom(arr) {
  return arr[Math.floor(Math.random() * arr.length)]
}

pickRandom(drinks).info()
类和实例 (Classes and Instances)

Classes can be used to abstract your code and share behavior. Always repeating the info function declaration in the last example is not good. Since classes and their instances are in fact objects, they behave in the same way. One thing to note however, is that declaring this in the constructor actually is a prediction for the future, when there will be an instance.

类可用于抽象代码并共享行为。 在最后一个示例中总是重复info函数声明是不好的。 由于类及其实例实际上是对象,因此它们的行为方式相同。 有一件事然而需要注意的是,宣称this在构造函数实际上是对未来的时候都会有一个实例的预测。

Let’s take a look:

让我们来看看:

class Coffee {
  constructor(strong) {
    this.strong = !!strong
  }
  info() {
    console.log(`This coffee is ${this.strong ? '' : 'not '}strong`)
  }
}

const strongCoffee = new Coffee(true)
const normalCoffee = new Coffee(false)

strongCoffee.info() // This coffee is strong
normalCoffee.info() // This coffee is not strong
陷阱:无缝嵌套的函数调用 (Pitfall: seamlessly nested function calls)

Sometimes, we end up in a context that we did not really expect. This can happen, when we unknowingly call the function inside another object context. A very common example is when using setTimeout or setInterval :

有时,我们最终遇到了一个我们没有真正期望的环境。 当我们在不知不觉中在另一个对象上下文中调用该函数时,可能会发生这种情况。 一个非常常见的示例是使用setTimeoutsetInterval

// BAD EXAMPLE
const coffee = {
  strong: true,
  amount: 120,
  drink: function() {
    setTimeout(function() {
      if (this.amount) this.amount -= 10
    }, 10)
  },
}

coffee.drink()

What do you think coffee.amount is?

您觉得coffee.amount是多少?

...

...

..

..

.

It is still 120 . First, we were inside the coffee object, since the drink method is declared inside of it. We just did setTimeout and nothing else. That’s exactly it.

现在仍然是120 。 首先,由于在对象内部声明了drink方法,因此我们位于coffee对象内部。 我们只是做了setTimeout而已。 就是这样

As I explained earlier, the setTimeout method is actually declared in the window object. When calling it, we actually switch context to the window again. That means that our instructions actually tried to change window.amount, but it ended up doing nothing because of the if-statement. To take care of that, we have to bind our functions (see below).

如前所述, setTimeout方法实际上是在window对象中声明的。 调用它时,我们实际上再次将上下文切换到window 。 这意味着我们的指令实际上试图更改window.amount ,但是由于if -statement而最终无济于事。 为了解决这个问题,我们必须bind我们的功能(见下文)。

React (React)

Using React, this will hopefully be a thing of the past soon, thanks to Hooks. At the moment, we still have to bind everything (more on that later) in one way or another. When I started out, I had no idea why I was doing it, but at this point, you should already know why it is necessary.

借助Hooks,使用React有望很快成为过去。 目前,我们仍然必须以一种或另一种方式bind所有内容(稍后再介绍)。 当我刚开始的时候,我不知道为什么要这么做,但是在这一点上,您应该已经知道为什么这样做是必要的。

Let’s have a look at two simple React class Components:

让我们看一下两个简单的React类组件:

// BAD EXAMPLE
import React from 'react'

class Child extends React.Component {
  render() {
    return <button onClick = {
      this.props.getCoffee
    } > Get some Coffee! < /button>
  }
}

class Parent extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      coffeeCount: 0,
    }
    // change to turn into good example – normally we would do:
    // this._getCoffee = this._getCoffee.bind(this)
  }
  render() {
    return ( <
      React.Fragment >
      <
      Child getCoffee = {
        this._getCoffee
      }
      /> < /
      React.Fragment >
    )
  }

  _getCoffee() {
    this.setState({
      coffeeCount: this.state.coffeeCount + 1,
    })
  }
}

When we now click on the button rendered by the Child , we will receive an error. Why? Because React changed our context when calling the _getCoffee method.

现在,当我们单击由Child渲染的按钮时,我们将收到一个错误。 为什么? 因为React在调用_getCoffee方法时更改了上下文。

I assume that React does call the render method of our Components in another context, through helper classes or similar (even though I would have to dig deeper to find out for sure). Therefore, this.state is undefined and we’re trying to access this.state.coffeeCount . You should receive something like Cannot read property coffeeCount of undefined .

我假设React确实通过辅助类或类似的东西在另一个上下文中调用了我们组件的render方法(即使我必须更深入地找出来才能确定)。 因此, this.state是未定义的,我们正在尝试访问this.state.coffeeCount 。 您应该收到类似Cannot read property coffeeCount of undefined

To solve the issue, you have to bind (we’ll get there) the methods in our classes, as soon as we pass them out of the component where they are defined.

要解决此问题,您必须在bind类中的方法传递到定义它们的组件之外后,将它们bind (到达那里)。

Let’s have a look at one more generic example:

让我们看一个更通用的示例:

// BAD EXAMPLE
class Viking {
  constructor(name) {
    this.name = name
  }

  prepareForBattle(increaseCount) {
    console.log(`I am ${this.name}! Let's go fighting!`)
    increaseCount()
  }
}

class Battle {
  constructor(vikings) {
    this.vikings = vikings
    this.preparedVikingsCount = 0

    this.vikings.forEach(viking => {
      viking.prepareForBattle(this.increaseCount)
    })
  }

  increaseCount() {
    this.preparedVikingsCount++
    console.log(`${this.preparedVikingsCount} vikings are now ready to fight!`)
  }
}

const vikingOne = new Viking('Olaf')
const vikingTwo = new Viking('Odin')

new Battle([vikingOne, vikingTwo])

We’re passing the increaseCount from one class to another. When we call the increaseCount method in Viking, we have already changed context and this actually points to the Viking , meaning that our increaseCount method will not work as expected.

我们将increaseCount从一个类传递到另一个类。 当我们调用increaseCount方法Viking ,我们已经改变了背景和this实际上指向Viking ,这意味着我们的increaseCount方法不会按预期方式工作。

解决方案—绑定 (Solution — bind)

The simplest solution for us is to bind the methods that will be passed out of our original object or class. There are different ways where you can bind functions, but the most common one (also in React) is to bind it in the constructor. So we would have to add this line in the Battle constructor before line 18:

对我们而言,最简单的解决方案是bind将从原始对象或类中传递出去的方法。 绑定函数有多种方法,但是最常见的一种方法(也在React中)是在构造函数中进行绑定。 因此,我们必须在第18行之前的Battle构造函数中添加此行:

this.increaseCount = this.increaseCount.bind(this)

You can bind any function to any context. This does not mean that you always have to bind the function to the context it is declared in (this is the most common case, however). Instead, you could bind it to another context. With bind , you always set the context for a function declaration. This means that all calls for that function will receive the bound context as this . There are two other helpers for setting the context.

您可以将任何功能绑定到任何上下文。 这并不意味着您总是必须将函数绑定到声明它的上下文中(不过,这是最常见的情况)。 相反,您可以将其绑定到另一个上下文。 使用bind ,您总是可以为函数声明设置上下文 。 这意味着,该函数的所有调用将获得绑定的上下文this 。 还有另外两个用于设置上下文的助手。

Arrow functions `() => {}` automatically bind the function to the declaration context
箭头函数`()=> {}`自动将函数绑定到声明上下文
申请并致电 (Apply and call)

They both do basically the same thing, just that the syntax is different. For both, you pass the context as first argument. apply takes an array for the other arguments, with call you can just separate other arguments by comma. Now what do they do? Both of these methods set the context for one specific function call. When calling the function without call , the context is set to the default context (or even a bound context). Here is an example:

它们的基本作用相同,只是语法不同。 对于这两种情况,您都将上下文作为第一个参数传递。 apply将其他参数作为一个数组,使用call可以用逗号分隔其他参数。 现在他们做什么? 这两种方法都为一个特定的函数调用设置了上下文。 在不call的情况下调用函数时,上下文设置为默认上下文(甚至是绑定上下文)。 这是一个例子:

class Salad {
  constructor(type) {
    this.type = type
  }
}

function showType() {
  console.log(`The context's type is ${this.type}`)
}

const fruitSalad = new Salad('fruit')
const greekSalad = new Salad('greek')

showType.call(fruitSalad) // The context's type is fruit
showType.call(greekSalad) // The context's type is greek

showType() // The context's type is undefined

Can you guess what the context of the last showType() call is?

您能猜出最后一个showType()调用的上下文是什么吗?

..

..

.

You’re right, it is the outermost scope, window . Therefore, type is undefined, there is no window.type

没错,它是最外面的作用域window 。 因此, typeundefined ,没有window.type

This is it, hopefully you now have a clear understanding on how to use this in JavaScript. Feel free to leave suggestions for the next article in the comments.

这是它,希望你现在有关于如何使用一个清晰的认识this在JavaScript中。 随时在评论中留下有关下一篇文章的建议。

About the Author: Lukas Gisder-Dubé co-founded and led a startup as CTO for 1 1/2 years, building the tech team and architecture. After leaving the startup, he taught coding as Lead Instructor at Ironhack and is now building a Startup Agency & Consultancy in Berlin. Check out dube.io to learn more.

关于作者:LukasGisder-Dubé与他人共同创立并领导了一家初创公司担任CTO长达1 1/2年,建立了技术团队和架构。 离开创业公司后,他在Ironhack担任首席讲师的编码课程,现在正在柏林建立创业公司和咨询公司。 查看dube.io了解更多信息。

翻译自: https://www.freecodecamp.org/news/how-to-understand-the-keyword-this-and-context-in-javascript-cd624c6b74b8/

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值