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(); // globalSe 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(); // localNo 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 = undefinedContexto 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(); // objectDefiniçã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.