'Arrow Functions' e 'Functions' são equivalentes / intercambiáveis?

520

As funções de seta no ES2015 fornecem uma sintaxe mais concisa.

  • Posso substituir todas as minhas declarações / expressões de funções por funções de seta agora?
  • O que eu tenho que procurar?

Exemplos:

Função construtora

function User(name) {
  this.name = name;
}

// vs

const User = name => {
  this.name = name;
};

Métodos de protótipo

User.prototype.getName = function() {
  return this.name;
};

// vs

User.prototype.getName = () => this.name;

Métodos de objeto (literal)

const obj = {
  getName: function() {
    // ...
  }
};

// vs

const obj = {
  getName: () => {
    // ...
  }
};

Retornos de chamada

setTimeout(function() {
  // ...
}, 500);

// vs

setTimeout(() => {
  // ...
}, 500);

Funções variáveis

function sum() {
  let args = [].slice.call(arguments);
  // ...
}

// vs
const sum = (...args) => {
  // ...
};
Felix Kling
fonte
5
Perguntas semelhantes sobre as funções de seta foram surgindo cada vez mais, com o ES2015 se tornando mais popular. Não achei que houvesse uma boa pergunta / resposta canônica para esse problema, então criei esse. Se você acha que já existe um bom, informe-me e fechá-lo-ei como duplicado ou excluído. Sinta-se livre para melhorar os exemplos ou adicionar novos.
Felix Kling
2
E o JavaScript ecma6 altera a função normal para seta ? Obviamente, uma pergunta normal nunca pode ser tão boa e genérica quanto uma escrita especificamente para ser canônica.
Bergi
Veja este exemplo de Plnkr. A variável thistem timesCalledincrementos muito diferentes apenas em 1 cada vez que o botão é chamado. O que responde à minha pergunta pessoal: .click( () => { } )e .click(function() { }) ambos criam o mesmo número de funções quando usadas em um loop, como você pode ver na contagem de Guid no Plnkr.
abbaf33f 12/09

Respostas:

750

tl; dr: Não! As funções de seta e as declarações / expressões de função não são equivalentes e não podem ser substituídas às cegas.
Se a função que deseja substituir se não usar this, argumentse não é chamado com new, então sim.


Como tantas vezes: depende . As funções de seta têm um comportamento diferente das declarações / expressões de função, então vamos dar uma olhada nas diferenças primeiro:

1. Lexical thisearguments

As funções de seta não têm suas próprias thisou argumentsvinculativas. Em vez disso, esses identificadores são resolvidos no escopo lexical como qualquer outra variável. Isso significa que dentro de uma função de seta thise argumentsreferir-se aos valores thise argumentsno ambiente em que a função de seta é definida (ou seja, "fora" da função de seta):

// Example using a function expression
function createObject() {
  console.log('Inside `createObject`:', this.foo);
  return {
    foo: 42,
    bar: function() {
      console.log('Inside `bar`:', this.foo);
    },
  };
}

createObject.call({foo: 21}).bar(); // override `this` inside createObject

// Example using a arrow function
function createObject() {
  console.log('Inside `createObject`:', this.foo);
  return {
    foo: 42,
    bar: () => console.log('Inside `bar`:', this.foo),
  };
}

createObject.call({foo: 21}).bar(); // override `this` inside createObject

No caso de expressão de função, thisrefere-se ao objeto que foi criado dentro do createObject. No caso função seta, thisrefere-se a thisdecreateObject si.

Isso torna as funções de seta úteis se você precisar acessar o thisambiente atual:

// currently common pattern
var that = this;
getData(function(data) {
  that.data = data;
});

// better alternative with arrow functions
getData(data => {
  this.data = data;
});

Observe que isso também significa que não é possível definir uma função de seta thiscom .bindou .call.

Se você não conhece muito bem this, considere ler

2. As funções de seta não podem ser chamadas com new

O ES2015 distingue entre funções que são chamadas e funções que são construídas . Se uma função é construtível, pode ser chamada com new, ie new User(). Se uma função puder ser chamada, ela poderá ser chamada sem new(ou seja, chamada de função normal).

Funções criadas através de declarações / expressões de função são construtíveis e chamadas.
As funções de seta (e métodos) só podem ser chamadas. classconstrutores são apenas construtíveis.

Se você estiver tentando chamar uma função não exigível ou construir uma função não construtível, receberá um erro de tempo de execução.


Sabendo disso, podemos afirmar o seguinte.

Substituível:

  • Funções que não usam thisou arguments.
  • Funções que são usadas com .bind(this)

Não substituível:

  • Funções de construtor
  • Função / métodos adicionados a um protótipo (porque eles geralmente usam this)
  • Funções variáveis ​​(se elas usarem arguments(veja abaixo))

Vamos dar uma olhada mais de perto usando seus exemplos:

Função construtora

Isso não funcionará porque as funções de seta não podem ser chamadas com new. Continue usando uma declaração / expressão de função ou use class.

Métodos de protótipo

Provavelmente não, porque os métodos de protótipo geralmente usam thispara acessar a instância. Se eles não usarem this, você poderá substituí-lo. No entanto, se você se preocupa principalmente com a sintaxe concisa, use classcom a sintaxe concisa do método:

class User {
  constructor(name) {
    this.name = name;
  }

  getName() {
    return this.name;
  }
}

Métodos de objeto

Da mesma forma para métodos em um literal de objeto. Se o método quiser fazer referência ao próprio objeto via this, continue usando expressões de função ou use a nova sintaxe do método:

const obj = {
  getName() {
    // ...
  },
};

Retornos de chamada

Depende. Você definitivamente deve substituí-lo se thisestiver usando o alias externo ou estiver usando .bind(this):

// old
setTimeout(function() {
  // ...
}.bind(this), 500);

// new
setTimeout(() => {
  // ...
}, 500);

Mas: se o código que chama o retorno de chamada definir explicitamente thisum valor específico, como geralmente ocorre com os manipuladores de eventos, especialmente com o jQuery, e o retorno de chamada usar this(ou arguments), você não poderá usar uma função de seta!

Funções variáveis

Como as funções de seta não têm suas próprias arguments, você não pode simplesmente substituí-las por uma função de seta. No entanto, o ES2015 apresenta uma alternativa ao uso arguments: o parâmetro rest .

// old
function sum() {
  let args = [].slice.call(arguments);
  // ...
}

// new
const sum = (...args) => {
  // ...
};

Pergunta relacionada:

Mais recursos:

Felix Kling
fonte
6
Talvez valha a pena mencionar que o léxico thistambém afeta supere que não .prototype.
Loganfsmyth
1
Também seria bom mencionar que eles não são sintaticamente intercambiáveis ​​- uma função de seta ( AssignmentExpression) não pode ser inserida em todos os lugares em que uma expressão de função ( PrimaryExpression) pode e faz com que as pessoas sejam visitadas com bastante frequência (especialmente porque houve análise erros nas principais implementações de JS).
JMM
@JMM: "ele tropeça nas pessoas com bastante frequência" você pode dar um exemplo concreto? Deslizando sobre a especificação, parece que os lugares onde você pode colocar um FE mas não um AF resultaria em erros de execução de qualquer maneira ...
Felix Kling
Claro, quero dizer coisas como tentar chamar imediatamente uma função de seta como uma expressão de função ( () => {}()) ou fazer algo assim x || () => {}. É isso que eu quero dizer: erros de tempo de execução (análise). (E, mesmo sendo esse o caso, com bastante frequência as pessoas pensam que o erro está errado.) Você está apenas tentando cobrir erros de lógica que passariam despercebidos porque não necessariamente erram quando analisados ​​ou executados? newQual é um erro de execução, certo?
JMM
Aqui estão alguns links que estão surgindo na natureza: substack / node-browserify # 1499 , babel / babel-eslint # 245 (esta é uma seta assíncrona, mas acho que é o mesmo problema básico) e vários problemas em Babel que são difíceis de encontrar agora, mas aqui está um T2847 .
JMM
11

As funções de seta => melhor recurso do ES6 até agora. Eles são uma adição tremendamente poderosa ao ES6, que eu uso constantemente.

Espere, você não pode usar a função de seta em qualquer lugar do código, isso não funcionará em todos os casos, como this onde as funções de seta não são utilizáveis. Sem dúvida, a função de seta é um ótimo complemento, traz simplicidade de código.

Mas você não pode usar uma função de seta quando um contexto dinâmico é necessário: definir métodos, criar objetos com construtores, obter o objetivo disso ao manipular eventos.

As funções de seta NÃO devem ser usadas porque:

  1. Eles não tem this

    Ele usa o "escopo lexical" para descobrir qual thisdeve ser o valor de " ". No escopo lexical de palavras simples, ele usa “ this” de dentro do corpo da função.

  2. Eles não tem arguments

    As funções de seta não têm um argumentsobjeto. Mas a mesma funcionalidade pode ser alcançada usando os parâmetros de descanso.

    let sum = (...args) => args.reduce((x, y) => x + y, 0) sum(3, 3, 1) // output - 7 `

  3. Eles não podem ser usados ​​com new

    As funções de seta não podem ser construtoras porque não possuem uma propriedade prototype.

Quando usar a função de seta e quando não:

  1. Não use para adicionar a função como uma propriedade no literal do objeto, porque não podemos acessar isso.
  2. Expressões de função são melhores para métodos de objeto. Arrow funções são melhores para callbacks ou métodos como map, reduceou forEach.
  3. Use declarações de função para funções que você chamaria pelo nome (porque são içadas).
  4. Use as funções de seta para retornos de chamada (porque eles tendem a ser mais tersos).
Ashutosh
fonte
2
o 2. Eles não têm argumentos, me desculpe, não é verdade, pode-se ter um argumento sem usar o operador ..., talvez você queira dizer que o teu não tem array como argumento
Carmine Tambascia
@CarmineTambascia Leia sobre o argumentsobjeto especial que não está disponível nas funções de seta aqui: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
vichle
0

Para usar as funções de seta function.prototype.call, criei uma função auxiliar no protótipo de objeto:

  // Using
  // @func = function() {use this here} or This => {use This here}
  using(func) {
    return func.call(this, this);
  }

uso

  var obj = {f:3, a:2}
  .using(This => This.f + This.a) // 5

Editar

Você não precisa de um ajudante. Você poderia fazer:

var obj = {f:3, a:2}
(This => This.f + This.a).call(undefined, obj); // 5
toddmo
fonte