Funções de seta e `this` em JavaScript: um guia completo

  • O valor de this Em JavaScript, isso depende de como a função é invocada, variando entre contexto global, métodos de objeto e uso em modo estrito.
  • As funções de seta não criam as suas próprias. thisEm vez disso, elas herdam lexicalmente a terminologia do domínio onde foram definidas, o que evita muitos problemas em funções de retorno de chamada.
  • É recomendável usar funções de seta em callbacks e métodos de array, mas evite usá-las como métodos de objeto, construtores ou manipuladores de eventos DOM que exigem this dinâmico.

Ilustração de uma seta e suas funções em JavaScript.

Se você já programa em JavaScript há algum tempo, certamente reconhecerá a palavra-chave. Isso já lhe causou mais de uma dor de cabeça.E desde que as funções de seta surgiram no ES6, as coisas ficaram ainda mais complicadas... ou mais simples, dependendo do ponto de vista.

Neste artigo, vamos analisar mais detalhadamente como isso funciona. Isso se aplica a funções tradicionais e funções de seta.Por que às vezes parece apontar para um objeto específico e outras vezes para o objeto global, e em que situações faz sentido usar funções de seta e em que situações é melhor evitá-las?

Que é exatamente this em JavaScript

A palavra reservada this É uma referência ao contexto de execução. da função que está sendo executada no momento. Ao contrário de outras linguagens, em JavaScript a decisão não se baseia em onde a função está definida, mas sim na execução da função. como invocar.

Isso significa que a mesma função pode ser chamada de maneiras diferentes e, em cada uma delas, this pode apontar para um objeto diferenteNão é algo que você possa mudar com uma atribuição direta (você não pode fazer this = algo), mas você pode influenciá-lo com mecanismos específicos, como call, apply y bind.

Além disso, seu comportamento varia entre Modo estrito e modo não estritoNo modo não estrito, se você chamar uma função "pura" (sem um objeto antes dela), this Geralmente é o objeto global (no navegador, window), enquanto no modo estrito pode ser undefinedEssa distinção é importante ao comparar exemplos de código de diferentes fontes.

Isso no contexto global e nas funções normais.

Nos navegadores, quando você não está dentro de nenhum módulo ou função, o contexto global é o objeto. windowe lá this apontar para esse objetoOu seja, se você digitar o seguinte no console:

console.log(this === window); // true en un entorno de navegador no estricto

Dentro de uma função declarada de forma “clássica” (função normal), o valor de this Depende de como essa função é chamada.Se você o invocar sem um objeto anterior, no modo não estrito this Geralmente é a global, e falando estritamente, será undefinedÉ por isso que, às vezes, ao migrar código de um site para outro, Isso já não é o que você esperava..

Isso ocorre em métodos de objeto definidos com funções normais.

Ao definir um método em um objeto usando a sintaxe tradicional, this Dentro do método, há uma referência ao próprio objeto. a partir da qual esse método foi invocado.

Por exemplo, se você tiver algo como:

const obj = {
  speak() {
    console.log(this);
  }
};
obj.speak();

A chamada obj.speak() faz que this Dentro de speak Seja o(a) objEsse é o comportamento que as pessoas geralmente esperam intuitivamente: o método fala "em nome" do objeto.

Se você usar uma função clássica em vez da sintaxe abreviada, o efeito será o mesmo, porque A chave está em como o método é invocado.Não importa se você usou a abreviação do método ou a palavra-chave. function dentro do objeto.

Isso ocorre em métodos definidos com funções de seta.

As coisas mudam quando você define o método com uma função de seta. Algo como:

const obj2 = {
  speak: () => {
    console.log(this);
  }
};
obj2.speak();

Neste caso, ao executar obj2.speak() você vai ver isso this Não é mais obj2mas o contexto lexical externo para esse objeto, que em um script de navegador clássico geralmente é o objeto global. window.

Isso é intrigante na primeira vez que você vê, porque espera que um método do objeto aponte para o próprio objeto. No entanto, As funções de seta não criam as suas próprias. thisEles herdam o valor de this do escopo em que foram definidos. Se esse escopo for global, eles herdarão o escopo global; se for outro, eles herdarão esse outro escopo.

Portanto, uma recomendação frequentemente repetida na documentação moderna é: Não utilize funções de seta como métodos de objeto. quando você precisar this Aponte para esse objeto.

Âmbito lexical de this funções de seta

A principal diferença entre funções normais e funções de seta é que estas últimas... ter um link lexical para thisResumindo: eles não decidem o que fazem. this não quando eles se telefonam, mas quando eles criar.

Imagine este exemplo:

const obj3 = {
  speak() {
    (() => {
      console.log(this);
    })();
  }
};
obj3.speak();

Aqui pode parecer que, como dentro speak Executamos uma função de seta, Isso deve "redefinir" para o global.Mas acontece exatamente o oposto: a função de seta captura o this da função que a envolveque neste caso é o método speak invocado como obj3.speak()Portanto, o valor de this O que é mostrado no console é o de obj3.

Isto é, As funções de seta não têm funções próprias. thismas sim reutilizar o que está em seu entorno imediato.Isso é incrivelmente útil em callbacks aninhados, temporizadores, promessas e em qualquer outro lugar onde, com funções clássicas, você tinha que lidar com... .bind ou com truques como const that = this;.

Exemplos práticos de perda e preservação de this

Um dos problemas clássicos em JavaScript é que, ao definir uma função dentro de um método, você perde a referência a this que apontava para o objeto e você acaba com a versão global ou com undefined.

Vamos considerar o caso típico de uso de setTimeout dentro de um método de um objeto com uma função tradicional:

const persona = {
  nombre: 'Agustin',
  decirNombre: function() {
    setTimeout(function() {
      console.log(this.nombre);
    }, 3000);
  }
};
persona.decirNombre(); // Muestra undefined

Aqui isso dentro da função passada para setTimeout Não é mais o objeto personaEssa função de retorno de chamada é executada no contexto global (em um navegador, window), assim que this.nombre Ele tenta ler uma propriedade na variável global, que não existe, e acaba sendo... undefined.

Antes da existência das funções de seta, uma solução comum era armazenar o valor de this em uma variável auxiliar Para "arrastá-lo" para dentro da função:

const persona = {
  nombre: 'Agustin',
  decirNombre: function() {
    let that = this; // aquí this es persona
    setTimeout(function() {
      console.log(that.nombre);
    }, 3000);
  }
};

Graças a essa variável, a referência correta ao objeto é mantida. Mas é um truque um tanto feio e repetitivo. Com funções de seta, esse problema é bastante simplificado:

const persona = {
  nombre: 'Agustin',
  decirNombre: function() {
    setTimeout(() => {
      console.log(this.nombre);
    }, 3000);
  }
};

Aqui, a função de seta não cria a sua própria. this, assim que herda o this do método decirNombreque é o objeto personaO resultado: “Agustin” é exibido corretamente sem a necessidade de variáveis ​​intermediárias ou .bind.

chamar, aplicar e vincular: controlando o valor de this

Além da maneira "natural" de definir o contexto com uma chamada de método, o JavaScript nos fornece ferramentas para forçar o valor de this em funções normais: call, apply y bind.

Métodos call() y apply() Elas invocam a função imediatamente, permitindo que você passe o objeto que deseja usar como this. A diferença é que call recebe os argumentos um por um, enquanto apply Eles são recebidos em um array. bind()Em vez disso, retorna uma nova função com o this “associado” ao valor que você indicouAssim você poderá ligar para ela mais tarde, quando lhe for conveniente.

Com funções de seta, no entanto, esses métodos não são úteis para fazer alterações. this porque seu valor está lexicalmente ligado. Você pode usar call, apply o bind para passar argumentos, mas não para modificar o contexto das funções de seta, o que é uma diferença muito importante em relação às funções regulares.

Sintaxe básica de funções de seta

Além do comportamento de thisAs funções de seta fornecem uma sintaxe mais compacta e expressiva Para muitas situações. A forma geral é:

(arg1, arg2, ..., argN) => expresion

Este formulário retorna automaticamente o resultado da expressão à direita da seta, portanto Não há necessidade de escrever a palavra return quando você só tem uma única expressão simples.

Alguns pontos comuns de sintaxe:

  • Sem parâmetros:
    () => 42 o incluso _ => 42 Se você não se importa com o nome do argumento.
  • Com um único parâmetro:
    Os parênteses são opcionais; você pode escrever x => x * 2 o (x) => x * 2.
  • Com múltiplos parâmetros:
    Os parênteses são obrigatórios: (x, y) => x + y.

Quando você precisar de várias declarações, poderá usar corpo em bloco com chaves:

const sumar = (x, y) => {
  const resultado = x + y;
  return resultado;
};

Neste caso, como existem chaves, Não existe mais nenhum retorno implícito.; se você não colocar returna função retornará undefinedIsso se aplica tanto a funções de seta quanto a funções tradicionais.

Retornar objetos literais com funções de seta

Existe um pequeno, mas muito comum detalhe sintático: quando uma função de seta retorna um objeto literal diretamenteVocê deve colocá-lo entre parênteses para que o interpretador não o confunda com um bloco.

Por exemplo:

x => ({ y: x })

Sem esses parênteses, o JavaScript interpretaria as chaves como o início do corpo da função, e não como um objeto. É um truque simples, mas que causa muitos erros bobos se você se esquecer dele.

Funções de seta: anônimas e sem protótipo.

As funções de seta são sintaticamente anônimoEles não têm nomes próprios, o que pode complicar um pouco as coisas. mensagens de depuração e erroPorque no rastreamento você não vê o identificador da função diretamente, a menos que o tenha atribuído a uma constante com um nome reconhecível.

Além disso, as funções de seta Eles não possuem propriedade. prototype e não podem ser utilizadas como empresas de construção.Se você tentar invocá-los com newVocê receberá um erro. Para criar objetos usando construtores ou classes, você ainda precisa usar funções ou sintaxe normais. class.

Outra consequência é que Eles não são adequados para padrões que exigem autorreferência interna., como algumas formas de recursão ou manipuladores de eventos que precisam se desinscrever usando this ou o próprio nome da função.

Onde as funções de seta brilham

A grande força das funções de seta reside precisamente na sua... ligação lexical de thisSão ideais em situações onde você deseja que o retorno de chamada passado para outra função mantenha a this da área circundante.

Por exemplo, em um objeto com um método que inicia um temporizador e precisa acessar continuamente propriedades do próprio objeto usando this:

const contador = {
  id: 42,
  iniciar() {
    setTimeout(() => {
      console.log(this.id); // this es contador
    }, 1000);
  }
};

Em ES5 era comum ter que colocar .bind(this) para o retorno de chamada ou salvar this em outra variável. Com funções de seta, O código fica mais limpo e mais próximo da intenção original..

Eles também são muito práticos com métodos de matriz, como map, filter, reduce e companhia, porque reduzir ruído sintático Quando a lógica da função é breve:

const numeros = [1, 2, 3];
const dobles = numeros.map(n => n * 2);

Quando usadas com moderação, essas formas compactas facilitam o acompanhamento do fluxo de dados num relance.

Quando evitar funções de seta

Embora as funções de seta sejam muito úteis, elas não substituem as funções regulares. Existem vários casos claros em que É melhor não usá-los.:

  • Métodos de objeto que dependem de this:
    Se você definir um método como saltos: () => { this.vidas--; } dentro de um objeto gato, this Não apontará para o gato, mas sim para o ambiente externo, e a propriedade não será atualizada como você espera.
  • Callbacks de eventos DOM que precisam de um this dinâmico:
    Em um manipulador como boton.addEventListener('click', () => { this.classList.toggle('on'); });, this Não será o botão pressionado, mas sim o contexto superior que provavelmente causará um erro de digitação.
  • Construtores ou funções que precisam prototype:
    Como não pode ser usado com newAs funções de seta não são adequadas para criar instâncias ou para padrões baseados em protótipos.

Em todos esses casos, um A função normal continua sendo a ferramenta apropriada. porque permite isso this Está dinamicamente ligado à forma como você invoca a função.

Se você se acostumar a escolher conscientemente entre funções normais e funções de seta, dependendo do que você precisa, this e a partir do contexto, Seu código ficará mais previsível e legível. para quem o guardar depois.

Em última análise, compreender como o valor de this Em JavaScript, e como as funções de seta herdam esse valor. A chave para parar de se debater com resultados inesperados, aproveitar os recursos sintáticos do ES6 e escrever métodos, callbacks e manipuladores de eventos que fazem exatamente o que você tinha em mente é entender o escopo léxico em que eles são criados.