Quando devo usar as funções de seta no ECMAScript 6?

407

A pergunta é direcionada a pessoas que pensaram no estilo do código no contexto do próximo ECMAScript 6 (Harmony) e que já trabalharam com o idioma.

Com () => {}e function () {}estamos obtendo duas maneiras muito semelhantes de escrever funções no ES6. Em outros idiomas, as funções lambda geralmente se distinguem por serem anônimas, mas no ECMAScript qualquer função pode ser anônima. Cada um dos dois tipos possui domínios de uso exclusivos (ou seja, quando é thisnecessário vincular explicitamente ou explicitamente não). Entre esses domínios, existe um grande número de casos em que qualquer notação funciona.

As funções de seta no ES6 têm pelo menos duas limitações:

  • Não trabalhe com newe não pode ser usado ao criarprototype
  • Corrigido o thislimite do escopo na inicialização

Essas duas limitações à parte, as funções de seta poderiam substituir teoricamente as funções regulares em quase qualquer lugar. Qual é a abordagem correta para usá-los na prática? As funções de seta devem ser usadas, por exemplo:

  • "em todos os lugares em que trabalham", ou seja, em todos os lugares uma função não precisa ser independente da thisvariável e não estamos criando um objeto.
  • apenas "em todos os lugares em que são necessários", ou seja, ouvintes de eventos, tempos limite, que precisam estar vinculados a um determinado escopo
  • com funções 'curtas', mas não com funções 'longas'
  • somente com funções que não contenham outra função de seta

O que estou procurando é uma diretriz para selecionar a notação de função apropriada na versão futura do ECMAScript. A diretriz precisará ser clara, para que possa ser ensinada aos desenvolvedores em uma equipe e ser consistente para que não exija constante refatoração para frente e para trás de uma notação de função para outra.

liso
fonte
33
Você considera Fixed this bound to scope at initialisationuma limitação?
thefourtheye
12
É uma vantagem, mas também pode ser uma limitação se você planeja reutilizar a função fora do contexto original. Por exemplo, ao adicionar uma função a uma classe dinamicamente via Object.prototype. O que quero dizer com 'limitação' é que alterar o valor de thisalgo que você pode fazer com funções regulares, mas não com funções de seta.
Lschoening
1
Honestamente, acho que as diretrizes de estilo de codificação são bastante opinativas. Não me interpretem mal, acho que são importantes, mas não existe uma única diretriz adequada para todos.
Felix Kling
Eu não acho que Fixed this bound to scope at initialisationseja uma limitação. :) Dê uma olhada neste artigo: exploringjs.com/es6/ch_arrow-functions.html
NgaNguyenDuy
3
@thefourtheye, "limitação" aqui significa "limitação porque um tradutor de código automático burro não pode substituir cegamente um pelo outro e assumir que tudo funcionará conforme o esperado".
Pacerier

Respostas:

322

Há um tempo, nossa equipe migrou todo o seu código (um aplicativo AngularJS de tamanho médio) para o JavaScript compilado usando o Traceur Babel . Agora estou usando a seguinte regra geral para funções no ES6 e além:

  • Use functionno escopo global e para Object.prototypepropriedades.
  • Use classpara construtores de objetos.
  • Use em =>qualquer outro lugar.

Por que usar as funções de seta em quase todos os lugares?

  1. Segurança do escopo: Quando as funções de seta são usadas de forma consistente, tudo é garantido para usar o mesmo thisObjectque a raiz. Se mesmo um único retorno de chamada de função padrão estiver misturado com várias funções de seta, é possível que o escopo fique confuso.
  2. Compacidade: as funções de seta são mais fáceis de ler e escrever. (Isso pode parecer opinativo, por isso darei alguns exemplos adiante).
  3. Clareza: Quando quase tudo é uma função de seta, qualquer regular functionimediatamente se destaca para definir o escopo. Um desenvolvedor sempre pode procurar a próxima functiondeclaração mais alta para ver o que thisObjecté.

Por que sempre usar funções regulares no escopo global ou no escopo do módulo?

  1. Para indicar uma função que não deve acessar o thisObject.
  2. O windowobjeto (escopo global) é melhor endereçado explicitamente.
  3. Muitas Object.prototypedefinições vivem no escopo global (pense String.prototype.truncateetc.) e geralmente têm que ser do tipo de functionqualquer maneira. O uso consistente functionno escopo global ajuda a evitar erros.
  4. Muitas funções no escopo global são construtores de objetos para definições de classe de estilo antigo.
  5. As funções podem ser nomeadas 1 . Isso tem dois benefícios: (1) É menos complicado escrever do function foo(){}que const foo = () => {}- em particular fora de outras chamadas de função. (2) O nome da função é exibido em rastreamentos de pilha. Embora seja tedioso nomear cada retorno de chamada interno, nomear todas as funções públicas é provavelmente uma boa idéia.
  6. As declarações de função são hasteadas (o que significa que elas podem ser acessadas antes de serem declaradas), que é um atributo útil em uma função de utilitário estático.


Construtores de objetos

Tentar instanciar uma função de seta gera uma exceção:

var x = () => {};
new x(); // TypeError: x is not a constructor

Uma vantagem importante das funções sobre as funções de seta é, portanto, que as funções funcionam como construtores de objetos:

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

No entanto, a definição de classe de rascunho 2 ES Harmony funcionalmente idêntica é quase tão compacta:

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

Espero que o uso da notação anterior acabe sendo desencorajado. A notação de construtor de objetos ainda pode ser usada por alguns para fábricas simples de objetos anônimos, onde objetos são gerados programaticamente, mas não por muito mais.

Onde um construtor de objetos é necessário, deve-se considerar a conversão da função para a, classcomo mostrado acima. A sintaxe também funciona com funções / classes anônimas.


Legibilidade das funções das setas

Provavelmente, o melhor argumento para aderir a funções regulares - dane-se a segurança do escopo - seria que as funções de seta são menos legíveis que as funções regulares. Se o seu código não estiver funcional em primeiro lugar, as funções de seta podem não parecer necessárias e, quando as funções de seta não são usadas de maneira consistente, elas ficam feias.

O ECMAScript mudou bastante desde que o ECMAScript 5.1 nos deu o funcional Array.forEach, Array.mape todos esses recursos funcionais de programação que nos utilizam usam funções onde for-loops teriam sido usados ​​antes. O JavaScript assíncrono decolou bastante. O ES6 também enviará um Promiseobjeto, o que significa ainda mais funções anônimas. Não há como voltar atrás para a programação funcional. No JavaScript funcional, as funções de seta são preferíveis às funções regulares.

Tomemos, por exemplo, este código (particularmente confuso) 3 :

function CommentController(articles) {
    this.comments = [];

    articles.getList()
        .then(articles => Promise.all(articles.map(article => article.comments.getList())))
        .then(commentLists => commentLists.reduce((a, b) => a.concat(b)));
        .then(comments => {
            this.comments = comments;
        })
}

O mesmo trecho de código com funções regulares:

function CommentController(articles) {
    this.comments = [];

    articles.getList()
        .then(function (articles) {
            return Promise.all(articles.map(function (article) { 
                return article.comments.getList();
            }));
        })
        .then(function (commentLists) {
            return commentLists.reduce(function (a, b) {
                return a.concat(b); 
            });
        })
        .then(function (comments) {
            this.comments = comments;
        }.bind(this));
}

Embora qualquer uma das funções de seta possa ser substituída por uma função padrão, haveria muito pouco a ganhar com isso. Qual versão é mais legível? Eu diria o primeiro.

Penso que a questão de usar funções de seta ou funções regulares se tornará menos relevante ao longo do tempo. A maioria das funções se tornará métodos de classe, que eliminam a functionpalavra - chave, ou se tornarão classes. As funções permanecerão em uso nas classes de correção através do Object.prototype. Nesse meio tempo, sugiro reservar a functionpalavra - chave para qualquer coisa que realmente deva ser um método ou classe.


Notas

  1. As funções de seta nomeada foram adiadas na especificação do ES6 . Eles ainda podem ser adicionados a uma versão futura.
  2. De acordo com a especificação preliminar, "Declarações / expressões de classe criam um par construtor de função / protótipo exatamente como para declarações de função" , desde que uma classe não use a extendpalavra - chave. Uma pequena diferença é que as declarações de classe são constantes, enquanto as de função não.
  3. Nota sobre blocos em funções de seta de instrução única: eu gosto de usar um bloco sempre que uma função de seta é chamada apenas para o efeito colateral (por exemplo, atribuição). Dessa forma, fica claro que o valor de retorno pode ser descartado.
liso
fonte
4
A outra vez que você gostaria de usar functioné quando você não quer thisser vinculado, certo? Meu cenário mais comum para isso é eventos, nos quais você pode thisse referir ao objeto (geralmente nó DOM) que acionou o evento.
Brett
13
Na verdade, acho que no exemplo 3, as funções regulares são mais legíveis. Mesmo não programadores poderiam adivinhar o que está acontecendo. Com as setas, você precisa saber exatamente como elas funcionam para entender esse exemplo. Talvez mais novas linhas ajudem o exemplo da seta, mas eu não sei. Apenas meus 2 centavos, mas setas me fazer encolher (mas eu não tê-los usado ainda, então eu pode ser convertido em breve.)
Spencer
3
@ Spencer, esse é um ponto justo. Da minha própria experiência, =>acaba ficando melhor com o tempo. Duvido que os não programadores se sentiriam muito diferentes sobre os dois exemplos. Se você estiver escrevendo o código ES2016, normalmente não usará muitas dessas funções de seta. Neste exemplo, usando async / waitit e uma compreensão de matriz, você acabaria com apenas uma função de seta na reduce()chamada.
Lyschoening #
3
Concordo plenamente com Spencer que as funções regulares são muito mais legíveis nesse exemplo.
precisa saber é o seguinte
2
Boa resposta, thx! pessoalmente, também uso setas no escopo global, tanto quanto possível. Isso me deixa com quase nenhuma 'função'. Para mim, uma 'função' no código significa um caso especial que precisa se destacar e ser cuidadosamente considerado.
Kofifus
81

De acordo com a proposta , as flechas tinham como objetivo "abordar e resolver vários pontos problemáticos comuns dos tradicionais Function Expression". Eles pretendiam melhorar as coisas ligando-se thislexicamente e oferecendo sintaxe concisa.

Contudo,

  • Não se pode sempre ligar thislexicamente
  • A sintaxe da função de seta é delicada e ambígua

Portanto, as funções de seta criam oportunidades de confusão e erros e devem ser excluídas do vocabulário de um programador JavaScript, substituídas por functionexclusivamente.

Em relação ao léxico this

this é problemático:

function Book(settings) {
    this.settings = settings;
    this.pages = this.createPages();
}
Book.prototype.render = function () {
    this.pages.forEach(function (page) {
        page.draw(this.settings);
    }, this);
};

As funções de seta pretendem corrigir o problema em que precisamos acessar uma propriedade thisdentro de um retorno de chamada. Já existem várias maneiras de fazer isso: Pode-se atribuir thisa uma variável, usar bindou usar o terceiro argumento disponível nos Arraymétodos agregados. No entanto, as setas parecem ser a solução mais simples, portanto o método pode ser refatorado da seguinte maneira:

this.pages.forEach(page => page.draw(this.settings));

No entanto, considere se o código usou uma biblioteca como jQuery, cujos métodos se ligam thisespecialmente. Agora, existem dois thisvalores para lidar:

Book.prototype.render = function () {
    var book = this;
    this.$pages.each(function (index) {
        var $page = $(this);
        book.draw(book.currentPage + index, $page);
    });
};

Devemos usar functionpara eachpoder ligar thisdinamicamente. Não podemos usar uma função de seta aqui.

Lidar com vários thisvalores também pode ser confuso, porque é difícil saber sobre o que thisum autor estava falando:

function Reader() {
    this.book.on('change', function () {
        this.reformat();
    });
}

O autor realmente pretendeu telefonar Book.prototype.reformat? Ou ele esqueceu de ligar thise pretendeu ligar Reader.prototype.reformat? Se mudarmos o manipulador para uma função de seta, nos perguntaremos da mesma forma se o autor queria a dinâmica this, mas escolheu uma seta porque ela se encaixava em uma linha:

function Reader() {
    this.book.on('change', () => this.reformat());
}

Pode-se colocar: "É excepcional que as setas às vezes possam ser a função errada a ser usada? Talvez se raramente precisemos de thisvalores dinâmicos , ainda seria bom usar setas na maioria das vezes."

Mas pergunte a si mesmo: "Valerá a pena depurar código e descobrir que o resultado de um erro foi causado por um 'caso de ponta?'" "Prefiro evitar problemas não apenas na maioria das vezes, mas 100% do tempo.

Existe uma maneira melhor: sempre use function(para que thispossa sempre ser ligado dinamicamente) e sempre faça referência thisatravés de uma variável. Variáveis ​​são lexicais e assumem muitos nomes. A atribuição thisa uma variável tornará suas intenções claras:

function Reader() {
    var reader = this;
    reader.book.on('change', function () {
        var book = this;
        book.reformat();
        reader.reformat();
    });
}

Além disso, sempre atribuir thisa uma variável (mesmo quando houver uma única thisou nenhuma outra função) garante que as intenções permaneçam claras, mesmo após a alteração do código.

Além disso, a dinâmica thisnão é excepcional. O jQuery é usado em mais de 50 milhões de sites (até o momento em que este artigo foi escrito em fevereiro de 2016). Aqui estão outras APIs vinculadas thisdinamicamente:

  • O Mocha (~ 120k de downloads ontem) expõe métodos para seus testes via this.
  • O Grunt (~ 63k downloads ontem) expõe métodos para criar tarefas via this.
  • O backbone (~ 22k downloads ontem) define os métodos de acesso this.
  • APIs de eventos (como os DOMs) se referem a um EventTargetcom this.
  • APIs prototípicas corrigidas ou estendidas referem-se a instâncias com this.

(Estatísticas via http://trends.builtwith.com/javascript/jQuery e https://www.npmjs.com .)

É provável que você thisjá exija ligações dinâmicas .

Um léxico thisé às vezes esperado, mas às vezes não; assim como uma dinâmica thisàs vezes é esperada, mas às vezes não. Felizmente, existe uma maneira melhor, que sempre produz e comunica a ligação esperada.

Em relação à sintaxe concisa

As funções de seta conseguiram fornecer uma "forma sintática mais curta" para as funções. Mas essas funções mais curtas o tornarão mais bem-sucedido?

É x => x * x"mais fácil de ler" do que function (x) { return x * x; }? Talvez seja, porque é mais provável que produza uma única linha curta de código. De acordo com Dyson A influência da velocidade de leitura e do comprimento da linha na eficácia da leitura na tela ,

Um comprimento de linha médio (55 caracteres por linha) parece suportar uma leitura eficaz em velocidades normais e rápidas. Isso produziu o mais alto nível de compreensão. . .

Justificativas semelhantes são feitas para o operador condicional (ternário) e para ifinstruções de linha única .

No entanto, você está realmente escrevendo as funções matemáticas simples anunciadas na proposta ? Meus domínios não são matemáticos, portanto minhas sub-rotinas raramente são tão elegantes. Em vez disso, normalmente vejo as funções de seta quebrando um limite de coluna e quebrando em outra linha devido ao editor ou guia de estilo, que anula "legibilidade" pela definição de Dyson.

Alguém pode colocar: "Que tal usar apenas a versão curta para funções curtas, quando possível?" Mas agora uma regra estilística contradiz uma restrição de linguagem: "Tente usar a menor notação de função possível, tendo em mente que às vezes apenas a notação mais longa será vinculada thisconforme o esperado". Essa confusão torna as flechas particularmente propensas ao mau uso.

Existem vários problemas com a sintaxe da função de seta:

const a = x =>
    doSomething(x);

const b = x =>
    doSomething(x);
    doSomethingElse(x);

Ambas as funções são sintaticamente válidas. Mas doSomethingElse(x);não está no corpo de b, é apenas uma afirmação de nível superior mal-recuada.

Ao expandir para o formulário de bloco, não há mais um implícito return, que poderia ser esquecido de restaurar. Mas a expressão pode ter apenas o objetivo de produzir um efeito colateral, então quem sabe se um explícito returnserá necessário daqui para frente?

const create = () => User.create();

const create = () => {
    let user;
    User.create().then(result => {
        user = result;
        return sendEmail();
    }).then(() => user);
};

const create = () => {
    let user;
    return User.create().then(result => {
        user = result;
        return sendEmail();
    }).then(() => user);
};

O que pode ser pretendido como um parâmetro de descanso pode ser analisado como o operador de spread:

processData(data, ...results => {}) // Spread
processData(data, (...results) => {}) // Rest

A atribuição pode ser confundida com argumentos padrão:

const a = 1;
let x;
const b = x => {}; // No default
const b = x = a => {}; // "Adding a default" instead creates a double assignment
const b = (x = a) => {}; // Remember to add parens

Os blocos parecem objetos:

(id) => id // Returns `id`
(id) => {name: id} // Returns `undefined` (it's a labeled statement)
(id) => ({name: id}) // Returns an object

O que isto significa?

() => {}

O autor pretendia criar um no-op ou uma função que retornasse um objeto vazio? (Com isso em mente, devemos colocar o local {depois =>? Devemos nos restringir apenas à sintaxe da expressão? Isso reduziria ainda mais a frequência das setas.)

=>parece <=e >=:

x => 1 ? 2 : 3
x <= 1 ? 2 : 3

if (x => 1) {}
if (x >= 1) {}

Para invocar uma expressão de função de seta imediatamente, é preciso colocar ()do lado de fora, mas a colocação ()do lado de dentro é válida e pode ser intencional.

(() => doSomething()()) // Creates function calling value of `doSomething()`
(() => doSomething())() // Calls the arrow function

Embora, se alguém escrever (() => doSomething()());com a intenção de escrever uma expressão de função chamada imediatamente, simplesmente nada acontecerá.

É difícil argumentar que as funções de seta são "mais compreensíveis", com todos os casos acima em mente. Um pode aprender todas as regras especiais necessárias para utilizar esta sintaxe. Isso realmente vale a pena?

A sintaxe de functioné excepcionalmente generalizada. Usar functionexclusivamente significa que a própria linguagem impede que alguém escreva códigos confusos. Para escrever procedimentos que devem ser sintaticamente entendidos em todos os casos, eu escolho function.

Em relação a uma diretriz

Você solicita uma orientação que precisa ser "clara" e "consistente". O uso de funções de seta acabará resultando em código sintaticamente válido e logicamente inválido, com ambas as formas de função entrelaçadas, significativa e arbitrariamente. Portanto, ofereço o seguinte:

Diretriz para Notação de Função no ES6:

  • Sempre crie procedimentos com function.
  • Sempre atribua thisa uma variável. Não use () => {}.
Jackson
fonte
5
Interessante escrever sobre a visão de um programador funcional em JavaScript. Não tenho certeza se concordo com o argumento de variáveis ​​privadas. OMI poucas pessoas realmente precisam deles; aqueles que precisam provavelmente também precisarão de outros recursos do contrato e optarão por uma extensão de linguagem como o TypeScript. Certamente posso ver o apelo de um em selfvez de um isso. Suas armadilhas de função de seta indicadas também são válidas e os mesmos padrões de outras instruções que podem ficar sem aparelho definitivamente se aplicam aqui; caso contrário, acho que, com o seu argumento, alguém poderia defender as funções das setas em todos os lugares.
Lyschoening
7
"Ter várias maneiras de fazer as coisas cria vetores desnecessários para discussões e divergências no local de trabalho e na comunidade linguística. Seria melhor se a gramática linguística não nos permitisse fazer más escolhas". Concordo muito. Nice writeup! Eu acho que as funções das setas são realmente um passo atrás. Em um tópico diferente, gostaria que meus colegas de trabalho parassem de tentar transformar JavaScript em C # com uma série de definições de protótipo. É nojento. Eu deveria anonimamente conectar seu post :)
Spencer
11
Muito bem escrito! Embora eu discorde da maioria de seus pontos, é importante considerar o ponto de vista oposto.
minexew
4
Não as funções de seta, mas o comportamento estranho de thisé o problema do Javascript. Em vez de estar implicitamente vinculado, thisdeve ser passado como um argumento explícito.
bob
5
" Sempre use a função (para que isso possa sempre ser ligado dinamicamente) e sempre faça referência a ela através de uma variável. ". Eu não poderia discordar mais!
48

As funções de seta foram criadas para simplificar a função scopee resolver a thispalavra - chave, tornando-a mais simples. Eles utilizam a =>sintaxe, que se parece com uma flecha.

Nota: Não substitui as funções existentes. Se você substituir toda sintaxe de função por funções de seta, isso não funcionará em todos os casos.

Vamos dar uma olhada na sintaxe ES5 existente. Se a thispalavra - chave estivesse dentro do método de um objeto (uma função que pertence a um objeto), a que ela se referiria?

var Actor = {
  name: 'RajiniKanth',
  getName: function() {
     console.log(this.name);
  }
};
Actor.getName();

O snippet acima se refere a um objecte imprime o nome "RajiniKanth". Vamos explorar o trecho abaixo e ver o que isso apontaria aqui.

var Actor = {
  name: 'RajiniKanth',
  movies: ['Kabali', 'Sivaji', 'Baba'],
  showMovies: function() {
   this.movies.forEach(function(movie) {
     alert(this.name + " has acted in " + movie);
   });
  }
};

Actor.showMovies();

Agora, e se a thispalavra - chave estivesse dentro method’s function?

Aqui, isso se referiria ao window objectdo inner functionque ele caiu scope. Porque this, sempre faz referência ao proprietário da função em que está, para este caso - já que agora está fora do escopo - a janela / objeto global.

Quando está dentro do objectmétodo de um - o functionproprietário do é o objeto. Assim, a palavra-chave this está vinculada ao objeto. No entanto, quando está dentro de uma função, independente ou dentro de outro método, sempre se refere ao window/globalobjeto.

var fn = function(){
  alert(this);
}

fn(); // [object Window]

Existem maneiras de resolver esse problema em ES5si mesmo, vamos analisar isso antes de mergulhar nas funções de seta do ES6 sobre como resolvê-lo.

Normalmente, você criaria uma variável fora da função interna do método. Agora, o ‘forEach’método obtém acesso thise, portanto, às object’spropriedades e seus valores.

var Actor = {
  name: 'RajiniKanth',
  movies: ['Kabali', 'Sivaji', 'Baba'],
  showMovies: function() {
   var _this = this;
   this.movies.forEach(function(movie) {
     alert(_this.name + " has acted in " + movie);
   });
  }
};

Actor.showMovies();

usando bindpara anexar a thispalavra - chave que se refere ao método ao method’s inner function.

var Actor = {
  name: 'RajiniKanth',
  movies: ['Kabali', 'Sivaji', 'Baba'],
  showMovies: function() {
   this.movies.forEach(function(movie) {
     alert(this.name + " has acted in " + movie);
   }.bind(this));
  }
};

Actor.showMovies();

Agora, com a ES6função de seta, podemos lidar com o lexical scopingproblema de uma maneira mais simples.

var Actor = {
  name: 'RajiniKanth',
  movies: ['Kabali', 'Sivaji', 'Baba'],
  showMovies: function() {
   this.movies.forEach((movie) => {
     alert(this.name + " has acted in " + movie);
   });
  }
};

Actor.showMovies();

Arrow functionssão mais parecidas com instruções de função, exceto que são bindpara isso parent scope. Se o argumento arrow function is in top scope, thisse referir a window/global scope, enquanto uma função de seta dentro de uma função regular terá esse argumento igual à sua função externa.

Com arrowfunções thisé vinculado ao anexo scopeno momento da criação e não pode ser alterado. O novo operador, vincular, chamar e aplicar não tem efeito nisso.

var asyncFunction = (param, callback) => {
  window.setTimeout(() => {
  callback(param);
  }, 1);
};

// With a traditional function if we don't control
// the context then can we lose control of `this`.
var o = {
  doSomething: function () {
  // Here we pass `o` into the async function,
  // expecting it back as `param`
  asyncFunction(o, function (param) {
  // We made a mistake of thinking `this` is
  // the instance of `o`.
  console.log('param === this?', param === this);
  });
  }
};

o.doSomething(); // param === this? false

No exemplo acima, perdemos o controle disso. Podemos resolver o exemplo acima usando uma referência variável thisou usando bind. Com o ES6, fica mais fácil gerenciar o thisque está vinculado lexical scoping.

var asyncFunction = (param, callback) => {
  window.setTimeout(() => {
  callback(param);
  }, 1);
};

var o = {
  doSomething: function () {
  // Here we pass `o` into the async function,
  // expecting it back as `param`.
  //
  // Because this arrow function is created within
  // the scope of `doSomething` it is bound to this
  // lexical scope.
  asyncFunction(o, (param) => {
  console.log('param === this?', param === this);
  });
  }
};

o.doSomething(); // param === this? true

Quando não usar as funções de seta

Dentro de um literal de objeto.

var Actor = {
  name: 'RajiniKanth',
  movies: ['Kabali', 'Sivaji', 'Baba'],
  getName: () => {
     alert(this.name);
  }
};

Actor.getName();

Actor.getNameé definido com uma função de seta, mas, na chamada, ele alerta indefinido porque this.nameé undefinedcomo o contexto permanece window.

Isso acontece porque a função seta liga o contexto lexicamente ao window objectescopo externo. A execução this.nameé equivalente a window.name, que é indefinido.

Protótipo de objeto

A mesma regra se aplica ao definir métodos em a prototype object. Em vez de usar uma função de seta para definir o método sayCatName, que traz um incorreto context window:

function Actor(name) {
  this.name = name;
}
Actor.prototype.getName = () => {
  console.log(this === window); // => true
  return this.name;
};
var act = new Actor('RajiniKanth');
act.getName(); // => undefined

Invocando Construtores

thisem uma chamada de construção é o objeto recém-criado. Ao executar o novo Fn (), o contexto do constructor Fné um novo objecto: this instanceof Fn === true.

this é configurado a partir do contexto anexo, ou seja, o escopo externo que o torna não atribuído ao objeto recém-criado.

var Message = (text) => {
  this.text = text;
};
// Throws "TypeError: Message is not a constructor"
var helloMessage = new Message('Hello World!');

Retorno de chamada com contexto dinâmico

A função Seta liga a contextdeclaração estaticamente e não é possível torná-la dinâmica. Anexar ouvintes de eventos a elementos DOM é uma tarefa comum na programação do lado do cliente. Um evento aciona a função manipuladora com isso como o elemento de destino.

var button = document.getElementById('myButton');
button.addEventListener('click', () => {
  console.log(this === window); // => true
  this.innerHTML = 'Clicked button';
});

thisé uma janela em uma função de seta definida no contexto global. Quando um evento de clique acontece, o navegador tenta chamar a função de manipulador com o contexto do botão, mas a função de seta não altera seu contexto predefinido. this.innerHTMLé equivalente window.innerHTMLe não faz sentido.

Você deve aplicar uma expressão de função, que permita alterar isso dependendo do elemento de destino:

var button = document.getElementById('myButton');
button.addEventListener('click', function() {
  console.log(this === button); // => true
  this.innerHTML = 'Clicked button';
});

Quando o usuário clica no botão, isso na função do manipulador é o botão Assim, this.innerHTML = 'Clicked button'modifica corretamente o texto do botão para refletir o status do clique.

Referências: https://dmitripavlutin.com/when-not-to-use-arrow-functions-in-javascript/

Thalaivar
fonte
Bem, devo admitir, que "o melhor está no meio" . Promovido para declaração, que as funções de seta não cobrirão nenhum caso de uso de função possível. Eles são realmente projetados para resolver apenas uma parte dos problemas comuns. Basta mudar para eles completamente será um exagero.
BlitZ
@DmitriPavlutin: Confira meu post atualizado, é uma coleção de muita coisa ... pode ser que eu deva postar uma referência.
Thalaivar
2
Seu código após a linha 'using bind para anexar a palavra-chave this que se refere ao método à função interna do método'. tem bugs nele. Você já testou o restante dos seus exemplos?
Isaac Pak
A única using bind to attach the this keyword that refers to the method to the method’s inner function.tem erros de sintaxe.
Coda Chang
Deve servar Actor = { name: 'RajiniKanth', movies: ['Kabali', 'Sivaji', 'Baba'], showMovies: function() { this.movies.forEach(function(movie){ alert(this.name + ' has acted in ' + movie); }.bind(this)) } }; Actor.showMovies();
Coda Chang
14

Funções de seta - o recurso ES6 mais usado até o momento ...

Uso: Todas as funções do ES5 devem ser substituídas pelas funções de seta do ES6, exceto nos seguintes cenários:

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

  1. Quando queremos içamento de funções
    • como as funções de seta são anônimas.
  2. Quando queremos usar this/ argumentsem uma função
    • como as funções de seta não têm this/ argumentspróprias, elas dependem de seu contexto externo.
  3. Quando queremos usar a função nomeada
    • como as funções de seta são anônimas.
  4. Quando queremos usar a função como um constructor
    • como as funções de seta não têm suas próprias this.
  5. Quando queremos adicionar função como uma propriedade no literal do objeto e usar o objeto nele
    • como não podemos acessar this(que deve ser o próprio objeto).

Vamos entender algumas das variantes das funções das setas para entender melhor:

Variante 1 : quando queremos passar mais de um argumento para uma função e retornar algum valor dela.

Versão ES5 :

var multiply = function (a,b) {
    return a*b;
};
console.log(multiply(5,6)); //30

Versão ES6 :

var multiplyArrow = (a,b) => a*b;
console.log(multiplyArrow(5,6)); //30

Nota: a functionpalavra - chave NÃO é necessária. =>É necessário. {}são opcionais, quando não fornecemos, {} returné adicionado implicitamente por JavaScript e, quando fornecemos {}, precisamos adicionar, returnse necessário.

Variante 2 : Quando queremos passar apenas um argumento para uma função e retornar algum valor dela.

Versão ES5 :

var double = function(a) {
    return a*2;
};
console.log(double(2)); //4

Versão ES6 :

var doubleArrow  = a => a*2;
console.log(doubleArrow(2)); //4

Nota: Ao passar apenas um argumento, podemos omitir parênteses ().

Variante 3 : quando NÃO queremos passar nenhum argumento para uma função e NÃO queremos retornar nenhum valor.

Versão ES5 :

var sayHello = function() {
    console.log("Hello");
};
sayHello(); //Hello

Versão ES6 :

var sayHelloArrow = () => {console.log("sayHelloArrow");}
sayHelloArrow(); //sayHelloArrow

Variante 4 : quando queremos retornar explicitamente das funções de seta.

Versão ES6 :

var increment = x => {
  return x + 1;
};
console.log(increment(1)); //2

Variante 5 : Quando queremos retornar um objeto das funções de seta.

Versão ES6 :

var returnObject = () => ({a:5});
console.log(returnObject());

Nota: Precisamos colocar o objeto entre parênteses, ()caso contrário, o JavaScript não pode diferenciar entre um bloco e um objeto.

Variante 6 : As funções de seta NÃO possuem arguments(um objeto semelhante a um array) próprias para as quais dependem do contexto externo arguments.

Versão ES6 :

function foo() {
  var abc = i => arguments[0];
  console.log(abc(1));
};    
foo(2); // 2

Nota: fooé uma função ES5, com uma argumentsmatriz como objeto e um argumento passado para ela é 2assim arguments[0]para fooé 2.

abcé um ES6 arrow função uma vez que não tem o seu próprio arguments, portanto, imprime arguments[0]de foocontexto externo lo de vez.

Variante 7 : As funções das flechas NÃO têm thispor si só, elas dependem do contexto externo parathis

Versão ES5 :

var obj5 = {
  greet: "Hi, Welcome ",
  greetUser : function(user) {
        setTimeout(function(){
        console.log(this.greet + ": " +  user); // "this" here is undefined.
        });
     }
};

obj5.greetUser("Katty"); //undefined: Katty

Nota: O retorno de chamada passado para setTimeout é uma função ES5 e possui uma função thisque é indefinida no use-strictambiente, portanto, obtemos saída:

undefined: Katty

Versão ES6 :

var obj6 = {
  greet: "Hi, Welcome ",
  greetUser : function(user) {
    setTimeout(() => console.log(this.greet + ": " +  user)); 
      // this here refers to outer context
   }
};

obj6.greetUser("Katty"); //Hi, Welcome: Katty

Nota: O retorno de chamada passado para setTimeouté uma função de seta ES6 e NÃO possui uma função própria, thisportanto, é extraído do contexto externo que é o greetUserque possui thise, obj6portanto, obtemos a saída:

Hi, Welcome: Katty

Diversos: Não podemos usar newcom funções de seta. As funções de seta não possuem prototypepropriedade. NÃO temos ligação de thisquando a função de seta é chamada por meio de applyou call.

Manishz90
fonte
6

Além das ótimas respostas até agora, gostaria de apresentar uma razão muito diferente pela qual as funções de seta são, em certo sentido, fundamentalmente melhores que as funções JavaScript "comuns". Para fins de discussão, vamos assumir temporariamente que usamos um verificador de tipos como o TypeScript ou o "Flow" do Facebook. Considere o seguinte módulo de brinquedo, que é um código ECMAScript 6 válido e anotações do tipo Flow: (incluirei o código não digitado, que resultaria realisticamente de Babel, no final desta resposta, para que possa realmente ser executado.)

export class C {
  n : number;
  f1: number => number; 
  f2: number => number;

  constructor(){
    this.n = 42;
    this.f1 = (x:number) => x + this.n;
    this.f2 = function (x:number) { return  x + this.n;};
  }
}

Agora veja o que acontece quando usamos a classe C de um módulo diferente, assim:

let o = { f1: new C().f1, f2: new C().f2, n: "foo" };
let n1: number = o.f1(1); // n1 = 43
console.log(n1 === 43); // true
let n2: number = o.f2(1); // n2 = "1foo"
console.log(n2 === "1foo"); // true, not a string!

Como você pode ver, o verificador de tipos falhou aqui: f2 deveria retornar um número, mas retornou uma string!

Pior, parece que nenhum verificador de tipo concebível pode lidar com funções JavaScript comuns (sem seta), porque o "this" de f2 não ocorre na lista de argumentos de f2, portanto, o tipo necessário para "this" não pode ser adicionado como uma anotação para f2.

Esse problema também afeta pessoas que não usam verificadores de tipo? Acho que sim, porque mesmo quando não temos tipos estáticos, pensamos como se eles estivessem lá. ("Os primeiros parâmetros devem ser um número, o segundo uma string" etc.). Um argumento "this" oculto que pode ou não ser usado no corpo da função dificulta nossa contabilidade mental.

Aqui está a versão executável e sem tipo, que seria produzida por Babel:

class C {
    constructor() {
        this.n = 42;
        this.f1 = x => x + this.n;
        this.f2 = function (x) { return x + this.n; };
    }
}

let o = { f1: new C().f1, f2: new C().f2, n: "foo" };
let n1 = o.f1(1); // n1 = 43
console.log(n1 === 43); // true
let n2 = o.f2(1); // n2 = "1foo"
console.log(n2 === "1foo"); // true, not a string!

Carsten Führmann
fonte
4

Eu ainda mantenho tudo o que escrevi na minha primeira resposta neste tópico. No entanto, minha opinião sobre o estilo do código se desenvolveu desde então, por isso tenho uma nova resposta para essa pergunta que se baseia na minha última.

Em relação ao léxico this

Em minha última resposta, evitei deliberadamente uma crença subjacente que tenho sobre essa linguagem, pois ela não estava diretamente relacionada ao argumento que estava apresentando. No entanto, sem que isso seja explicitamente declarado, posso entender por que muitas pessoas simplesmente se recusam a recomendar que não use flechas, quando acham as flechas tão úteis.

Minha crença é a seguinte: não devemos usar thisem primeiro lugar. Portanto, se uma pessoa evita deliberadamente o uso thisem seu código, o thisrecurso " lexical " das setas tem pouco ou nenhum valor. Além disso, sob a premissa de que thisé uma coisa ruim, o tratamento de Arrow thisé menos uma "coisa boa"; em vez disso, é mais uma forma de controle de danos para outro recurso de linguagem ruim.

Eu acho que isso não ocorre para algumas pessoas, mas mesmo para aqueles a quem ocorre, elas devem sempre trabalhar em bases de código onde thisaparece cem vezes por arquivo e um pouco (ou muito) de controle de danos é tudo uma pessoa razoável poderia esperar. Portanto, as flechas podem ser boas, de certa forma, quando melhoram uma situação ruim.

Mesmo que seja mais fácil escrever código com thissetas do que sem elas, as regras para o uso de setas permanecem muito complexas (consulte: thread atual). Portanto, as diretrizes não são "claras" nem "consistentes", conforme solicitado. Mesmo que os programadores conheçam as ambiguidades das setas, acho que elas encolhem os ombros e as aceitam de qualquer maneira, porque o valor do lexical as thisobscurece.

Tudo isso é um prefácio para a seguinte realização: se não se usa this, então a ambiguidade sobre thisessas setas normalmente causa se torna irrelevante. As setas se tornam mais neutras nesse contexto.

Em relação à sintaxe concisa

Quando escrevi minha primeira resposta, eu acreditava que mesmo a adesão servil às melhores práticas era um preço a pagar, se isso significasse que eu poderia produzir um código mais perfeito. Mas acabei percebendo que a dispersão pode servir como uma forma de abstração que também pode melhorar a qualidade do código - o suficiente para justificar, às vezes, o afastamento das melhores práticas.

Em outras palavras: caramba, também quero funções de uma linha!

Em relação a uma diretriz

Com a possibilidade de thisfunções de flecha neutra, e a falta de valor valer a pena, ofereço a seguinte orientação mais branda:

Diretriz para Notação de Função no ES6:

  • Não use this.
  • Use declarações de função para funções que você chamaria pelo nome (porque são içadas).
  • Use as funções de seta para retornos de chamada (porque eles tendem a ser mais tersos).
Jackson
fonte
Concorde 100% com a seção "Diretriz para notação de funções no ES6" na parte inferior - principalmente com funções de elevação e retorno de chamada em linha. boa resposta!
Jeff McCloud
1

De uma maneira simples,

var a =20; function a(){this.a=10; console.log(a);} 
//20, since the context here is window.

Outra instância:

var a = 20;
function ex(){
this.a = 10;
function inner(){
console.log(this.a); //can you guess the output of this line.
}
inner();
}
var test = new ex();

Resp: O console imprimiria 20.

A razão é que, sempre que uma função é executada, sua própria pilha é criada, neste exemplo, a exfunção é executada com o newoperador para que um contexto seja criado e, quando innerexecutado, o JS criaria uma nova pilha e executaria a innerfunção, global contextembora haja um contexto local.

Portanto, se queremos que a innerfunção tenha um contexto local ex, precisamos vincular o contexto à função interna.

As setas resolvem esse problema, em vez de pegar o que Global contexteles pegam, local contextse houver algum. No given example,que vai demorar new ex()como this.

Portanto, em todos os casos em que a ligação é explícita, as setas resolvem o problema por padrão.

Rajendra kumar Vankadari
fonte
1

As funções de seta ou Lambdas foram introduzidas no ES 6. Além de sua elegância na sintaxe mínima, a diferença funcional mais notável é o escopo de this dentro de uma função de seta

Nas expressões de função regular , a thispalavra-chave é vinculada a diferentes valores com base no contexto em que é chamada.

Nas funções de seta , thisé lexicamente vinculado, o que significa que fecha sobre thiso escopo no qual a função de seta foi definida (escopo pai) e não muda, não importa onde e como é chamada / invocada.

Limitações Arrow-Functions como métodos em um objeto

// this = global Window
let objA = {
 id: 10,
 name: "Simar",
 print () { // same as print: function() 
  console.log(`[${this.id} -> ${this.name}]`);
 }
}
objA.print(); // logs: [10 -> Simar]
objA = {
 id: 10,
 name: "Simar",
 print: () => {
  // closes over this lexically (global Window)
  console.log(`[${this.id} -> ${this.name}]`);
 }
};
objA.print(); // logs: [undefined -> undefined]

No caso de objA.print()quando o print()método foi definido usando regular function , ele funcionou resolvendo thiscorretamente a objAchamada de método, mas falhou quando definido como uma =>função de seta . Isso ocorre porque, thisem uma função regular, quando invocado como método em um objeto ( objA), é o próprio objeto. No entanto, no caso de uma função de seta, thisfica lexicamente vinculado ao thisescopo de fechamento em que foi definido (global / Window no nosso caso) e permanece o mesmo durante sua chamada como método on objA.

Vantagens de uma seta-funcionar sobre funções regulares no (s) método (s) de um objeto, MAS somente quando thisse espera que seja corrigido e vinculado na definição de tempo.

/* this = global | Window (enclosing scope) */

let objB = {
 id: 20,
 name: "Paul",
 print () { // same as print: function() 
  setTimeout( function() {
    // invoked async, not bound to objB
    console.log(`[${this.id} -> ${this.name}]`);
  }, 1)
 }
};
objB.print(); // logs: [undefined -> undefined]'
objB = {
 id: 20,
 name: "Paul",
 print () { // same as print: function() 
  setTimeout( () => {
    // closes over bind to this from objB.print()
    console.log(`[${this.id} -> ${this.name}]`);
  }, 1)
 }
};
objB.print(); // logs: [20 -> Paul]

No caso em objB.print()que o print()método é definido como uma função que chama console.log([$ {this.id} -> {this.name}] de )forma assíncrona como retorno de chamada setTimeout , thisresolvida corretamente objBquando uma função de seta foi usada como retorno de chamada, mas falhou quando o retorno de chamada foi definido como uma função regular. É porque a =>função de seta passou para setTimeout(()=>..)fechada thislexicamente a partir de seu pai ou seja. invocação da objB.print()qual o definiu. Em outras palavras-, a seta =>função passado para a setTimeout(()==>...ligado para objBque o seu thisporque o na invocação de objB.print() thisera objBsi.

Poderíamos facilmente usar Function.prototype.bind(), para fazer com que a chamada de retorno definida como uma função regular funcionasse, vinculando-a à correta this.

const objB = {
 id: 20,
 name: "Singh",
 print () { // same as print: function() 
  setTimeout( (function() {
    console.log(`[${this.id} -> ${this.name}]`);
  }).bind(this), 1)
 }
}
objB.print() // logs: [20 -> Singh]

No entanto, as funções de seta são úteis e menos propensas a erros no caso de retornos assíncronos, nos quais sabemos thiso momento da definição de funções à qual ela recebe e deve estar vinculada.

Limitação das funções de seta nas quais isso precisa mudar entre as invocações

A qualquer momento, precisamos de uma função que thispossa ser alterada no momento da invocação, não podemos usar as funções de seta.

/* this = global | Window (enclosing scope) */

function print() { 
   console.log(`[${this.id} -> {this.name}]`);
}
const obj1 = {
 id: 10,
 name: "Simar",
 print // same as print: print
};
obj.print(); // logs: [10 -> Simar]
const obj2 = {
 id: 20,
 name: "Paul",
};
printObj2 = obj2.bind(obj2);
printObj2(); // logs: [20 -> Paul]
print.call(obj2); // logs: [20 -> Paul]

Nenhuma das const print = () => { console.log(opções acima funcionará com a função de seta [$ {this.id} -> {this.name}], );}pois thisnão pode ser alterada e permanecerá vinculada ao thisescopo do anexo em que foi definida (global / Window). Em todos esses exemplos, invocamos a mesma função com objetos diferentes ( obj1e obj2) um após o outro, ambos criados após a print()declaração da função.

Estes foram exemplos inventados, mas vamos pensar em mais alguns exemplos da vida real. Se tivéssemos que escrever nosso reduce()método semelhante ao que funciona arrays , novamente não podemos defini-lo como um lambda, porque ele precisa inferir a thispartir do contexto de invocação, ie. a matriz na qual foi invocada

Por esse motivo, as constructorfunções nunca podem ser definidas como funções de seta, pois thisuma função de construtor não pode ser definida no momento de sua declaração. Sempre que uma função construtora é chamada com newpalavra - chave, é criado um novo objeto que é vinculado a essa chamada específica.

Além disso, quando estruturas ou sistemas aceitam que funções de retorno de chamada sejam invocadas posteriormente com contexto dinâmico this , não podemos usar funções de seta, pois novamente thisprecisamos mudar a cada invocação. Essa situação geralmente ocorre com os manipuladores de eventos DOM

'use strict'
var button = document.getElementById('button');
button.addEventListener('click', function {
  // web-api invokes with this bound to current-target in DOM
  this.classList.toggle('on');
});
var button = document.getElementById('button');
button.addEventListener('click', () => {
  // TypeError; 'use strict' -> no global this
  this.classList.toggle('on');
});

Essa também é a razão pela qual em estruturas como Angular 2+ e Vue.js esperam que os métodos de ligação do componente do modelo sejam funções / métodos regulares, thispois sua chamada é gerenciada pelas estruturas das funções de ligação. (O Angular usa o Zone.js para gerenciar o contexto assíncrono para invocações de funções de ligação do modelo de exibição).

Por outro lado, em React , quando queremos passar o método de um componente como manipulador de eventos, por exemplo <input onChange={this.handleOnchange} />, devemos definir handleOnchanage = (event)=> {this.props.onInputChange(event.target.value);}como uma função de seta, como para cada chamada, queremos que essa seja a mesma instância do componente que produziu o JSX para renderizado Elemento DOM.


Este artigo também está disponível na minha publicação Medium . Se você gosta do artile, ou tem algum comentário e sugestão, bata palmas ou deixe comentários no Medium .

Simar Singh
fonte