Como entender a palavra-chave this e o contexto em JavaScript

Conforme mencionado em um de meus artigos anteriores, dominar totalmente o JavaScript pode ser uma jornada longa. Você pode ter se encontrado this
em sua jornada como desenvolvedor de JavaScript. Quando comecei, vi pela primeira vez ao usar eventListeners
e com o jQuery. Mais tarde, tive que usá-lo frequentemente com o React e tenho certeza que você também fez. Isso não significa que eu realmente entendi o que é e como assumi-lo totalmente.
No entanto, é muito útil dominar o conceito por trás disso e, quando abordado com uma mente clara, também não é muito difícil.
Investigando nisso
Explicarthis
pode causar muita confusão, simplesmente pela nomenclatura da palavra-chave.
this
está intimamente ligado ao contexto em que você está, em seu programa. Vamos começar pelo topo. Em nosso navegador, se você apenas digitar this
no console, obterá o window
-objeto, o contexto mais externo para o seu JavaScript. No Node.js, se fizermos:
console.log(this)
acabamos com {}
um objeto vazio. Isso é um pouco estranho, mas parece que o Node.js se comporta dessa forma. Se você fizer
(function() { console.log(this); })();
entretanto, você receberá o global
objeto, o contexto mais externo. Nesse contexto setTimeout
, setInterval
são armazenados. Sinta-se à vontade para brincar um pouco com ele para ver o que você pode fazer com ele. A partir daqui, quase não há diferença entre o Node.js e o navegador. Eu estarei usando window
. Lembre-se de que no Node.js será o global
objeto, mas isso realmente não faz diferença.

Lembre-se: o contexto só faz sentido dentro das funções
Imagine que você escreve um programa sem aninhar nada nas funções. Você simplesmente escreveria uma linha após a outra, sem descer em estruturas específicas. Isso significa que você não precisa saber onde está. Você está sempre no mesmo nível.
Quando você começa a ter funções, você pode ter diferentes níveis de seu programa e this
representa onde você está, qual objeto chamou a função.
Acompanhar o objeto chamador
Vamos dar uma olhada no exemplo a seguir e ver como this
muda dependendo do contexto:
const coffee = { strong: true, info: function() { console.log(`The coffee is ${this.strong ? '' : 'not '}strong`) }, } coffee.info() // The coffee is strong
Como chamamos uma função que é declarada dentro do coffee
objeto, nosso contexto muda exatamente para esse objeto. Agora podemos acessar todas as propriedades desse objeto por meio de this
. Em nosso exemplo acima, também podemos referenciá-lo diretamente fazendo coffee.strong
. Fica mais interessante quando não sabemos em que contexto, em que objeto estamos ou quando as coisas simplesmente ficam um pouco mais complexas. Dê uma olhada no seguinte exemplo:
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 e instâncias
As classes podem ser usadas para abstrair seu código e compartilhar o comportamento. Sempre repetir a info
declaração de função no último exemplo não é bom. Como as classes e suas instâncias são de fato objetos, elas se comportam da mesma maneira. Uma coisa a se notar, entretanto, é que declarar this
no construtor na verdade é uma previsão para o futuro, quando haverá uma instância.
Vamos dar uma olhada:
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
Armadilha: chamadas de função perfeitamente aninhadas
Às vezes, acabamos em um contexto que realmente não esperávamos. Isso pode acontecer, quando involuntariamente chamamos a função dentro de outro contexto de objeto. Um exemplo muito comum é ao usar setTimeout
ou setInterval
:
// BAD EXAMPLE const coffee = { strong: true, amount: 120, drink: function() { setTimeout(function() { if (this.amount) this.amount -= 10 }, 10) }, } coffee.drink()
O que você acha que coffee.amount
é?
...
..
.
Ainda está 120
. Primeiro, estávamos dentro do coffee
objeto, já que o drink
método é declarado dentro dele. Nós apenas fizemos setTimeout
e nada mais. É exatamente isso.
Como expliquei anteriormente, o setTimeout
método é realmente declarado no window
objeto. Ao chamá-lo, na verdade mudamos o contexto para o window
novamente. Isso significa que nossas instruções realmente tentaram mudar window.amount
, mas acabaram não fazendo nada por causa da if
instrução-. Para cuidar disso, temos bind
nossas funções (veja abaixo).
Reagir
Usando o React, espero que isso seja coisa do passado em breve, graças a Hooks. No momento, ainda temos de bind
tudo (mais sobre isso depois) de uma forma ou de outra. Quando eu comecei, não tinha ideia do por que estava fazendo isso, mas a essa altura, você já deve saber por que é necessário.
Vamos dar uma olhada em dois componentes simples da classe React:
// BAD EXAMPLE import React from 'react' class Child extends React.Component { render() { return Get some Coffee! } } 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 ( ) } _getCoffee() { this.setState({ coffeeCount: this.state.coffeeCount + 1, }) } }
Quando agora clicarmos no botão renderizado pelo Child
, receberemos um erro. Por quê? Porque o React mudou nosso contexto ao chamar o _getCoffee
método.
Presumo que o React chame o método render de nossos componentes em outro contexto, por meio de classes auxiliares ou semelhantes (embora eu tivesse que cavar mais fundo para descobrir com certeza). Portanto, this.state
está indefinido e estamos tentando acessar this.state.coffeeCount
. Você deve receber algo parecido Cannot read property coffeeCount of undefined
.
Para resolver o problema, você tem que bind
(chegaremos lá) os métodos em nossas classes, assim que os passarmos para fora do componente onde estão definidos.

Vamos dar uma olhada em mais um exemplo genérico:
// 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])
Estamos passando increaseCount
de uma aula para outra. Quando chamamos o increaseCount
método em Viking
, já mudamos o contexto e this
realmente apontamos para o Viking
, o que significa que nosso increaseCount
método não funcionará como esperado.
Solução - ligar
A solução mais simples para nós são bind
os métodos que serão transmitidos de nosso objeto ou classe original. Existem diferentes maneiras de vincular funções, mas a mais comum (também no React) é vinculá-las ao construtor. Portanto, teríamos que adicionar esta linha no Battle
construtor antes da linha 18:
this.increaseCount = this.increaseCount.bind(this)
Você pode vincular qualquer função a qualquer contexto. Isso não significa que você sempre terá que vincular a função ao contexto em que foi declarada (no entanto, esse é o caso mais comum). Em vez disso, você pode vinculá-lo a outro contexto. Com bind
, você sempre define o contexto para uma declaração de função . Isso significa que todas as chamadas para essa função receberão o contexto vinculado como this
. Existem dois outros auxiliares para definir o contexto.

Inscreva-se e ligue
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:
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?
…
..
.
You’re right, it is the outermost scope, window
. Therefore, type
is undefined
, there is no window.type
É isso, espero que agora você tenha um entendimento claro sobre como usar this
em JavaScript. Fique à vontade para deixar sugestões para o próximo artigo nos comentários.
Sobre o autor: Lukas Gisder-Dubé cofundou e liderou uma startup como CTO por 1 ano e meio, construindo a equipe de tecnologia e arquitetura. Depois de deixar a startup, ele ensinou programação como instrutor líder na Ironhack e agora está construindo uma agência e consultoria de startups em Berlim. Confira dube.io para saber mais.
