Palavra-chave this em JavaScript
O comportamento da palavra-chave this
depende do contexto em que ela é utilizada e se é usado o modo estrito ou não.
Contexto Global e objeto globalThis
No contexto global, this
refere-se ao objeto global. O que é o "objeto global" no JavaScript? Isso depende do ambiente em que o código é executado. Em um navegador web, this
representa o objeto window
, que representa a janela do navegador. Em um ambiente Node.js, this
representa o objeto global. Para web workers, this representa o objeto self
.
Por exemplo, no navegador, ao executar o seguinte código:
console.log(this);
Você verá algo semelhante a:
Window {window: Window, self: Window, document: document, name: "", location: Location, …}
O padrão ES2020 introduziu o objeto globalThis
, que permite se referir ao contexto global, independentemente do ambiente ou situação em que o código é executado:
console.log(globalThis);
Contexto de Função
Dentro de uma função, this
refere-se ao contexto externo. Para funções definidas no contexto global, isso se refere ao objeto globalThis
. Por exemplo:
function foo() {
var bar = "local";
console.log(this.bar);
}
var bar = "global";
foo(); // global
Se não usássemos this
, a referência seria para a variável local definida dentro da função:
function foo() {
var bar = "local";
console.log(bar);
}
var bar = "global";
foo(); // local
No entanto, se estivermos no modo estrito, this seria undefined
:
"use strict";
function foo() {
var bar = "local";
console.log(this.bar);
}
var bar = "global";
foo(); // erro: this = undefined
Contexto de Objeto
No contexto de um objeto, incluindo seus métodos, a palavra-chave this refere-se ao próprio objeto:
const obj = {
bar: "object",
foo: function () {
console.log(this.bar);
},
};
var bar = "global";
obj.foo(); // object
Definição Dinâmica de Contexto
Em JavaScript, a palavra-chave this
dentro de uma função é determinada pelo contexto em que a função é chamada, não onde é definida. Vamos explorar um exemplo mais complexo:
function foo() {
var bar = "foo_bar";
console.log(this.bar);
}
const obj1 = { bar: "obj1_bar", foo: foo };
const obj2 = { bar: "obj2_bar", foo: foo };
var bar = "global_bar";
foo(); // "global_bar"
obj1.foo(); // "obj1_bar"
obj2.foo(); // "obj2_bar"
Neste exemplo, a variável global bar
é definida com o valor "global_bar"
. A função foo, por outro lado, define uma variável local bar com "foo_bar"
. Contudo, o valor de bar
que é impresso pela função foo
é determinado pelo contexto externo, ou seja, o contexto global por padrão, portanto, imprime a variável global "global_bar"
porque o script é executado em modo não estrito.
No caso de objetos, eles definem seu próprio contexto que inclui a propriedade bar
. Assim, quando o método foo
é chamado como um método de obj1
ou obj2
, o contexto externo é o dos objetos, respectivamente.
Considere outra situação para ilustrar possíveis confusões:
var bar = "global_bar";
const obj1 = {
bar: "obj1_bar",
foo: function() {
console.log(this.bar); // "obj1_bar"
{
{;
const obj2 = { bar: "obj2_bar", foo: obj1.foo }; // "obj2_bar"
const foo = obj1.foo; // Refere-se à função foo definida em obj1
obj1.foo(); // "obj1_bar"
obj2.foo(); // "obj2_bar"
foo(); // "global_bar"
Na situação discutida, a função foo
dentro de obj1
é definida como segue:
const obj1 = {
bar: "obj1_bar",
foo: function () {
console.log(this.bar); // "obj1_bar"
},
};
Aqui, a função foo usa this.bar
, que pega o valor de bar
do contexto externo de obj1
, isto é, "obj1_bar"
.
O objeto obj2
utiliza a função foo de obj1
:
const obj2 = { bar: "obj2_bar", foo: obj1.foo };
Quando obj1.foo
é chamada como parte de obj2
, ela procura this.bar
no contexto externo, que agora é obj2
. Por isso, this.bar
será "obj2_bar"
.
O mesmo acontece quando foo
é atribuída a uma variável global e chamada:
const foo = obj1.foo;
Neste caso, a função busca this.bar
no contexto global, onde a variável bar
é "global_bar"
.
Contexto em Funções Aninhadas
Quando uma função é chamada dentro de outra função, o contexto da função interna será o mesmo da função externa, a menos que seja explicitamente modificado:
var bar = "global bar";
function foo() {
var bar = "foo bar";
function moo() {
console.log(this.bar);
}
moo();
}
foo(); // "global bar"
A função moo
usa this.bar que, apesar de moo ser chamada dentro de foo
, é determinado pelo contexto de chamada de foo
, que é o contexto global, resultando em "global bar"
.
Uso de call() e apply()
Esses métodos permitem que você especifique explicitamente o contexto para o qual this deve se referir:
function foo() {
console.log(this.bar);
}
var obj = { bar: "obj_bar" };
var bar = "global_bar";
foo(); // "global_bar"
foo.apply(obj); // "obj_bar"
foo.call(obj); // "obj_bar"
Método bind
O método bind
é usado para criar uma nova função que, quando chamada, terá seu this definido permanentemente para um valor específico, independente de como a função é chamada:
function foo() {
console.log(this.bar);
}
const obj = { bar: "object" };
var bar = "global";
foo(); // "global"
const func = foo.bind(obj);
func(); // "object"
this e Funções Arrow
Nas funções arrow, o this
é determinado pelo contexto de encapsulamento, e não pelo ambiente em que a função arrow é definida. Vejamos o seguinte exemplo:
const person = {
name: "Tom",
say: () => console.log(`Meu nome é ${this.name}`),
};
person.say(); // "Meu nome é "
Neste exemplo, a função arrow say()
tenta acessar a propriedade this.name
, mas o this
aqui refere-se ao objeto global (objeto window no navegador), pois funções arrow não têm seu próprio this
. Como não há uma variável global name definida, o resultado impresso é apenas.
Meu nome é
Alteremos um pouco o exemplo:
const person = {
name: "Tom",
hello() {
console.log("Olá");
let say = () => console.log(`Meu nome é ${this.name}`);
say();
},
};
person.hello();
Agora, a função arrow say
está definida dentro do método hello()
. O this
para este método refere-se ao objeto person
, onde o método está definido. Assim, em say
, o this também se refere ao objeto person
, e this.name
acessa a propriedade name deste objeto. O resultado da execução será:
Olá Meu nome é Tom
Apesar de as funções arrow adicionarem algumas complicações no trabalho com this
, elas também podem resolver várias questões. Considere o seguinte código:
const school = {
title: "Oxford",
courses: ["JavaScript", "TypeScript", "Java", "Go"],
printCourses() {
this.courses.forEach(function (course) {
console.log(this.title, course);
});
},
};
school.printCourses();
A função printCourses
percorre todos os cursos e tenta prefixar cada um com o valor da propriedade title
. No entanto, o resultado impresso será:
undefined "JavaScript" undefined "TypeScript" undefined "Java" undefined "Go"
O valor de this.title
é undefined
porque o this
dentro da função passada para forEach
é o contexto global, e não o objeto school
.
Uma solução é capturar this
em uma variável externa:
const school = {
title: "Oxford",
courses: ["JavaScript", "TypeScript", "Java", "Go"],
printCourses() {
const that = this;
this.courses.forEach(function (course) {
console.log(that.title, course);
});
},
};
school.printCourses();
Funções arrow oferecem uma abordagem mais elegante:
const school = {
title: "Oxford",
courses: ["JavaScript", "TypeScript", "Java", "Go"],
printCourses() {
this.courses.forEach((course) => console.log(this.title, course));
},
};
school.printCourses();
Com funções arrow, o contexto para a função é sempre o do objeto school
, eliminando a necessidade de variáveis adicionais para passar o contexto.