Quando dovrei usare le funzioni Arrow in ECMAScript 6?


407

La domanda è rivolta a persone che hanno pensato allo stile del codice nel contesto del prossimo ECMAScript 6 (Harmony) e che hanno già lavorato con la lingua.

Con () => {}e function () {}stiamo ottenendo due modi molto simili per scrivere funzioni in ES6. In altre lingue le funzioni lambda spesso si distinguono per essere anonime, ma in ECMAScript qualsiasi funzione può essere anonima. Ognuno dei due tipi ha domini di utilizzo univoci (vale a dire quando thisdeve essere associato esplicitamente o esplicitamente non essere associato). Tra questi domini c'è un vasto numero di casi in cui entrambe le notazioni lo faranno.

Le funzioni freccia in ES6 hanno almeno due limitazioni:

  • Non lavorare con newe non può essere utilizzato durante la creazioneprototype
  • Risolto il thisproblema con l'ambito all'inizializzazione

A parte queste due limitazioni, le funzioni freccia potrebbero teoricamente sostituire le funzioni regolari quasi ovunque. Qual è l'approccio giusto usandoli in pratica? Dovrebbero essere utilizzate le funzioni freccia, ad esempio:

  • "ovunque funzionino", cioè ovunque una funzione non deve essere agnostica rispetto alla thisvariabile e non stiamo creando un oggetto.
  • solo "ovunque siano necessari", ovvero ascoltatori di eventi, timeout, che devono essere vincolati a un determinato ambito
  • con funzioni "brevi" ma non con funzioni "lunghe"
  • solo con funzioni che non contengono un'altra funzione freccia

Quello che sto cercando è una guida per selezionare la notazione di funzione appropriata nella futura versione di ECMAScript. Le linee guida dovranno essere chiare, in modo che possano essere insegnate agli sviluppatori in un team e coerenti in modo da non richiedere un refactoring costante avanti e indietro da una notazione di funzione a un'altra.


33
Consideri Fixed this bound to scope at initialisationuna limitazione?
thefourtheye

12
È un vantaggio, ma può anche essere una limitazione se si prevede di riutilizzare la funzione al di fuori del contesto originale. Ad esempio, quando si aggiunge una funzione a una classe in modo dinamico tramite Object.prototype. Quello che intendo per "limitazione" è che cambiare il valore di thisè qualcosa che puoi fare con le funzioni regolari ma non con le funzioni freccia.
lyschoening

1
Onestamente penso che le linee guida sullo stile di codifica siano piuttosto supposte. Non fraintendetemi, penso che siano importanti, ma non esiste una sola linea guida adatta a tutti.
Felix Kling

Non credo Fixed this bound to scope at initialisationsia una limitazione. :) Dai un'occhiata a questo articolo: exploringjs.com/es6/ch_arrow-functions.html
NgaNguyenDuy

3
@thefourtheye, "limitazione" qui significa "limitazione perché un muto traduttore di codice automatico non potrebbe sostituirsi ciecamente l'uno con l'altro e supporre che tutto funzionerà come previsto".
Pacerier,

Risposte:


322

Qualche tempo fa il nostro team ha migrato tutto il suo codice (un'app AngularJS di medie dimensioni) su JavaScript compilato utilizzando Traceur Babel . Ora sto usando la seguente regola empirica per le funzioni in ES6 e oltre:

  • Utilizzare functionnell'ambito globale e per le Object.prototypeproprietà.
  • Utilizzare classper i costruttori di oggetti.
  • Usa =>altrove.

Perché usare le funzioni freccia quasi ovunque?

  1. Sicurezza dell'ambito: quando le funzioni delle frecce vengono utilizzate in modo coerente, è garantito che tutto usi lo stesso thisObjectdella radice. Se anche una singola funzione standard di callback è mescolata con un sacco di funzioni freccia c'è una possibilità che l'ambito si incasini.
  2. Compattezza: le funzioni freccia sono più facili da leggere e scrivere. (Questo può sembrare supponente, quindi fornirò alcuni esempi più avanti).
  3. Chiarezza: quando quasi tutto è una funzione freccia, qualsiasi normale functionsporge immediatamente per definire l'ambito. Uno sviluppatore può sempre cercare la functionfrase successiva per vedere di cosa si thisObjecttratta.

Perché utilizzare sempre funzioni regolari nell'ambito globale o nell'ambito del modulo?

  1. Per indicare una funzione che non dovrebbe accedere a thisObject.
  2. L' windowoggetto (ambito globale) viene indirizzato in modo esplicito.
  3. Molte Object.prototypedefinizioni vivono nell'ambito globale (pensa String.prototype.truncateecc.) E quelle generalmente devono functioncomunque essere di tipo . L'uso coerente functionnell'ambito globale aiuta a evitare errori.
  4. Molte funzioni nell'ambito globale sono costruttori di oggetti per definizioni di classe vecchio stile.
  5. Le funzioni possono essere nominate 1 . Ciò ha due vantaggi: (1) Scrivere meno è scomodo function foo(){}di const foo = () => {}- in particolare al di fuori di altre chiamate di funzione. (2) Il nome della funzione viene visualizzato nelle tracce dello stack. Mentre sarebbe noioso nominare ogni callback interno, nominare tutte le funzioni pubbliche è probabilmente una buona idea.
  6. Le dichiarazioni di funzioni vengono sollevate (nel senso che è possibile accedervi prima che vengano dichiarate), che è un attributo utile in una funzione di utilità statica.


Costruttori di oggetti

Il tentativo di creare un'istanza di una funzione freccia genera un'eccezione:

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

Un vantaggio chiave delle funzioni rispetto alle funzioni freccia è quindi che le funzioni doppie come costruttori di oggetti:

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

Tuttavia, la definizione della classe di tiraggio 2 ES Harmony funzionalmente identica è quasi altrettanto compatta:

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

Mi aspetto che l'uso della precedente notazione alla fine sarà scoraggiato. La notazione del costruttore di oggetti può ancora essere utilizzata da alcuni per semplici fabbriche di oggetti anonimi in cui gli oggetti sono generati a livello di programmazione, ma non per molto altro.

Laddove è necessario un costruttore di oggetti, è consigliabile convertire la funzione in classcome mostrato sopra. La sintassi funziona anche con funzioni / classi anonime.


Leggibilità delle funzioni delle frecce

L'argomento probabilmente migliore per attenersi alle funzioni regolari - dannosa la sicurezza dell'ambito - sarebbe che le funzioni freccia sono meno leggibili rispetto alle funzioni normali. Se il tuo codice non è funzionale in primo luogo, le funzioni freccia potrebbero non sembrare necessarie e quando le funzioni freccia non vengono utilizzate in modo coerente sembrano brutte.

ECMAScript è cambiato un po 'da quando ECMAScript 5.1 ci ha fornito le funzionalità Array.forEach, Array.mape tutte queste funzionalità di programmazione funzionale che ci hanno fatto usare funzioni dove i for-loop sarebbero stati usati in precedenza. JavaScript asincrono è decollato parecchio. ES6 spedirà anche un Promiseoggetto, il che significa funzioni ancora più anonime. Non si può tornare indietro per la programmazione funzionale. In JavaScript funzionale, le funzioni freccia sono preferibili rispetto alle funzioni normali.

Prendi ad esempio questo (particolarmente confuso) codice 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;
        })
}

Lo stesso pezzo di codice con funzioni regolari:

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));
}

Mentre una qualsiasi delle funzioni freccia può essere sostituita da una funzione standard, ci sarebbe molto poco da guadagnare nel farlo. Quale versione è più leggibile? Direi il primo.

Penso che la domanda se utilizzare le funzioni freccia o le funzioni regolari diventerà meno rilevante nel tempo. La maggior parte delle funzioni diventeranno metodi di classe, che eliminano la functionparola chiave, o diventeranno classi. Le funzioni rimarranno in uso per le classi di patching tramite Object.prototype. Nel frattempo suggerisco di riservare la functionparola chiave per qualsiasi cosa che dovrebbe essere davvero un metodo di classe o una classe.


Appunti

  1. Le funzioni freccia denominate sono state rinviate nelle specifiche ES6 . Potrebbero ancora essere aggiunte una versione futura.
  2. Secondo la bozza di specifica "Le dichiarazioni / espressioni di classe creano una coppia funzione / prototipo del costruttore esattamente come per le dichiarazioni di funzione" purché una classe non usiextend parola chiave. Una differenza minore è che le dichiarazioni di classe sono costanti, mentre non lo sono le dichiarazioni di funzione.
  3. Nota sui blocchi nelle funzioni freccia a singola istruzione: mi piace usare un blocco ovunque venga chiamata una funzione freccia per il solo effetto collaterale (es. Assegnazione). In questo modo è chiaro che il valore restituito può essere scartato.

4
L'altra volta che vorresti usare functionè quando non vuoi thisessere legato, giusto? Il mio scenario più comune per questo è eventi, in cui potresti voler thisfare riferimento all'oggetto (di solito nodo DOM) che ha attivato l'evento.
Brett,

13
In realtà penso nell'esempio 3, le funzioni regolari sono più leggibili. Anche i non programmatori potrebbero divinare ciò che sta accadendo. Con le frecce, devi sapere esattamente come funzionano per capire quell'esempio. Forse più newline potrebbero aiutare l'esempio della freccia, ma non lo so. Solo i miei 2 centesimi ma le frecce mi fanno rabbrividire (ma non li ho ancora usati, quindi potrei essere convertito presto.)
Spencer,

3
@Spencer questo è un punto giusto. Dalla mia esperienza, =>finisce per apparire migliore con il tempo. Dubito che i non programmatori si sentirebbero diversamente sui due esempi. Se stai scrivendo il codice ES2016, normalmente non finirai nemmeno per usare queste molte funzioni freccia. In questo esempio, usando async / await e una comprensione dell'array, si otterrebbe una sola funzione freccia nella reduce()chiamata.
lischoening,

3
Sono completamente d'accordo con Spencer sul fatto che le funzioni regolari sono molto più leggibili in questo esempio.
jonschlinkert,

2
Buona risposta, grazie! personalmente uso anche le frecce nell'ambito globale il più possibile. Questo mi lascia quasi senza "funzione". Per me una "funzione" nel codice significa un caso speciale che deve sporgere ed essere attentamente considerato.
kofifus,

81

Secondo la proposta , le frecce miravano a "affrontare e risolvere diversi punti dolenti comuni del tradizionale Function Expression". Intendevano migliorare le cose vincolandothis lessicamente e offrendo sintassi concisa.

Però,

  • Non si può legare in modo coerente this lessicale
  • La sintassi della funzione freccia è delicata e ambigua

Pertanto, le funzioni freccia creano opportunità di confusione ed errori e dovrebbero essere escluse dal vocabolario di un programmatore JavaScript, sostituito functionesclusivamente.

Per quanto riguarda lessicale this

this è problematico:

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

Le funzioni freccia intendono risolvere il problema in cui è necessario accedere a una proprietà thisall'interno di un callback. Esistono già diversi modi per farlo: si potrebbe assegnare thisa una variabile, usare bindo usare il terzo argomento disponibile sui Arraymetodi aggregati. Eppure le frecce sembrano essere la soluzione più semplice, quindi il metodo potrebbe essere riformulato in questo modo:

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

Tuttavia, considera se il codice utilizzava una libreria come jQuery, i cui metodi si legano in modo thisspeciale. Ora, ci sono due thisvalori da affrontare:

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

Dobbiamo usare functionper eachlegarethis dinamicamente. Non possiamo usare una funzione freccia qui.

Trattare con più thisvalori può anche essere fonte di confusione, perché è difficile sapere qualethis autore stesse parlando:

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

L'autore intendeva davvero chiamare Book.prototype.reformat? O ha dimenticato di legare thise ha intenzione di chiamare Reader.prototype.reformat? Se cambiamo il gestore in una funzione freccia, ci chiederemo allo stesso modo se l'autore volesse la dinamicathis , eppure abbia scelto una freccia perché si adattava su una riga:

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

Ci si potrebbe porre: "È eccezionale che le frecce a volte possano essere la funzione sbagliata da usare? Forse se avessimo bisogno solo raramente di thisvalori dinamici , sarebbe comunque bene usare le frecce per la maggior parte del tempo."

Ma chiediti questo: "Ne varrebbe la pena eseguire il debug del codice e scoprire che il risultato di un errore è stato causato da un" caso limite "?" Preferirei evitare problemi non solo la maggior parte delle volte, ma Il 100% delle volte.

C'è un modo migliore: usa sempre function(quindi thispuò sempre essere associato dinamicamente) e fai sempre riferimento thistramite una variabile. Le variabili sono lessicali e assumono molti nomi. L'assegnazione thisa una variabile renderà chiare le tue intenzioni:

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

Inoltre, l' assegnazione semprethis a una variabile (anche quando è presente una singola thiso nessuna altra funzione) garantisce che le intenzioni rimangano chiare anche dopo la modifica del codice.

Inoltre, la dinamica thisnon è certo eccezionale. jQuery è utilizzato su oltre 50 milioni di siti Web (al momento della stesura di questo documento nel febbraio 2016). Ecco altre API che si legano thisdinamicamente:

  • Mocha (~ 120k download ieri) espone metodi per i suoi test tramite this.
  • Grunt (~ 63k download ieri) espone metodi per compilare attività tramite this.
  • Backbone (~ 22k download ieri) definisce i metodi di accesso this.
  • Le API degli eventi (come i DOM) si riferiscono a EventTargetcon this.
  • Le API prototipo che sono patchate o estese si riferiscono a istanze con this.

(Statistiche tramite http://trends.builtwith.com/javascript/jQuery e https://www.npmjs.com .)

Probabilmente thisavrai già bisogno di associazioni dinamiche .

A thisvolte è previsto un lessico , ma a volte no; proprio come a thisvolte ci si aspetta una dinamica , ma a volte no. Per fortuna, esiste un modo migliore, che produce e comunica sempre l'associazione prevista.

Per quanto riguarda la sintassi concisa

Le funzioni freccia sono riuscite a fornire una "forma sintattica più corta" per le funzioni. Ma queste funzioni più brevi ti renderanno più efficace?

x => x * x" È più facile da leggere" di function (x) { return x * x; }? Forse lo è, perché è più probabile che produca un'unica riga di codice breve. Rispetto a Dyson's L'influenza della velocità di lettura e della lunghezza della linea sull'efficacia della lettura dallo schermo ,

Una lunghezza di riga media (55 caratteri per riga) sembra supportare una lettura efficace a velocità normale e veloce. Ciò ha prodotto il massimo livello di comprensione. . .

Giustificazioni simili vengono fatte per l'operatore condizionale (ternario) e per le ifistruzioni a riga singola .

Tuttavia, stai davvero scrivendo le semplici funzioni matematiche pubblicizzate nella proposta ? I miei domini non sono matematici, quindi le mie subroutine raramente sono così eleganti. Piuttosto, comunemente vedo che le funzioni delle frecce infrangono un limite di colonna e si spostano su un'altra riga a causa dell'editor o della guida di stile, che annulla la "leggibilità" secondo la definizione di Dyson.

Si potrebbe pensare: "Che ne dici di usare la versione breve per funzioni brevi, quando possibile?" Ma ora una regola stilistica contraddice un vincolo linguistico: "Cerca di usare la notazione di funzione più breve possibile, tenendo presente che a volte si legherà solo la notazione più lungathis come previsto". Tale conflazione rende le frecce particolarmente soggette ad uso improprio.

Esistono numerosi problemi con la sintassi della funzione freccia:

const a = x =>
    doSomething(x);

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

Entrambe queste funzioni sono sintatticamente valide. Ma doSomethingElse(x);non è nel corpo di b, è solo un'affermazione di alto livello scarsamente indentata.

Quando si espande nella forma di blocco, non c'è più un implicito return, che si potrebbe dimenticare di ripristinare. Ma l'espressione può solo avere lo scopo di produrre un effetto collaterale, quindi chissà se un esplicito returnsarà necessario andare avanti?

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);
};

Ciò che può essere inteso come parametro di riposo può essere analizzato come operatore di diffusione:

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

L'assegnazione può essere confusa con argomenti predefiniti:

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

I blocchi sembrano oggetti:

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

Cosa significa questo?

() => {}

L'autore intendeva creare una no-op o una funzione che restituisce un oggetto vuoto? (Con questo in mente, dovremmo mai posizionarci {dopo =>? Dovremmo limitarci solo alla sintassi dell'espressione? Ciò ridurrebbe ulteriormente la frequenza delle frecce.)

=>sembra <=e >=:

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

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

Per invocare immediatamente un'espressione della funzione freccia, è necessario posizionarla ()all'esterno, ma la collocazione ()all'interno è valida e potrebbe essere intenzionale.

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

Sebbene, se si scrive (() => doSomething()());con l'intenzione di scrivere un'espressione di funzione immediatamente invocata, semplicemente non accadrà nulla.

È difficile sostenere che le funzioni delle frecce siano "più comprensibili" tenendo conto di tutti i casi di cui sopra. Si potrebbero imparare tutte le regole speciali richieste per utilizzare questa sintassi. Ne vale davvero la pena?

La sintassi di functionè generalizzata in modo ineccepibile. Usare functionesclusivamente significa che il linguaggio stesso impedisce di scrivere codice confuso. Per scrivere procedure che dovrebbero essere sintatticamente comprese in tutti i casi, scelgo function.

Per quanto riguarda una linea guida

Si richiede una linea guida che deve essere "chiara" e "coerente". L'uso delle funzioni freccia alla fine porterà a un codice sintatticamente valido, logicamente non valido, con entrambe le forme di funzione intrecciate, in modo significativo e arbitrario. Pertanto, offro quanto segue:

Linee guida per la notazione delle funzioni in ES6:

  • Creare sempre procedure con function.
  • Assegna sempre thisa una variabile. Non usare () => {}.

5
Interessante scrivere sul punto di vista di un programmatore funzionale su JavaScript. Non sono sicuro di essere d'accordo con l'argomento delle variabili private. IMO poche persone ne hanno davvero bisogno; quelli che lo faranno probabilmente avranno bisogno anche di altre funzionalità di contratto e comunque opteranno per un'estensione del linguaggio come TypeScript. Posso certamente vedere l'appello di un selfinvece di questo. Anche le insidie ​​della tua funzione di freccia dichiarate sono tutte valide e gli stessi standard di altre affermazioni che possono andare senza parentesi graffe si applicano anche qui; in caso contrario, penso che con il tuo argomento si potrebbe anche difendere le funzioni delle frecce ovunque.
lischoening,

7
"Avere molteplici modi di fare le cose crea vettori inutili per argomenti e dissensi sul posto di lavoro e nella comunità linguistica. Sarebbe meglio se la grammatica della lingua non ci permettesse di fare scelte sbagliate". Sono molto d'accordo. Bel writeup! Penso che le funzioni delle frecce siano in realtà un passo indietro. Su un argomento diverso, vorrei che i miei colleghi smettessero di provare a trasformare JavaScript in C # con una serie di definizioni .prototype. È disgustoso. Dovrei collegare anonimamente il tuo post :)
Spencer,

11
Molto ben scritto! Anche se non sono d'accordo con la maggior parte dei tuoi punti, è importante considerare il punto di vista opposto.
minexew

4
Non le funzioni freccia ma lo strano comportamento di thisè il problema di Javascript. Invece di essere implicitamente vincolato, thisdovrebbe essere passato come argomento esplicito.
bob

5
Msgstr " Usa sempre la funzione (quindi questa può sempre essere associata in modo dinamico) e fai sempre riferimento a questa tramite una variabile. ". Non potrei essere più in disaccordo!

48

Le funzioni freccia sono state create per semplificare la funzione scopee risolvere la thisparola chiave rendendola più semplice. Usano la =>sintassi, che sembra una freccia.

Nota: non sostituisce le funzioni esistenti. Se si sostituisce ogni sintassi delle funzioni con le funzioni freccia, non funzionerà in tutti i casi.

Diamo un'occhiata alla sintassi ES5 esistente, se la thisparola chiave fosse all'interno del metodo di un oggetto (una funzione che appartiene a un oggetto), a cosa si riferirebbe?

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

Lo snippet sopra riportato farebbe riferimento a an objecte stamperebbe il nome "RajiniKanth". Esploriamo lo snippet di seguito e vediamo cosa segnalerebbe qui.

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

Actor.showMovies();

E se la thisparola chiave fosse dentromethod’s function ?

Qui questo si riferirebbe al fatto window objectche è inner functioncaduto fuori scope. Perchéthis , fa sempre riferimento al proprietario della funzione in cui si trova, in questo caso - dal momento che non rientra nell'ambito di applicazione - l'oggetto window / global.

Quando è all'interno di un objectmetodo, il functionproprietario è l'oggetto. Pertanto, questa parola chiave è associata all'oggetto. Tuttavia, quando si trova all'interno di una funzione, indipendente o all'interno di un altro metodo, farà sempre riferimento window/globalall'oggetto.

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

fn(); // [object Window]

Ci sono modi per risolvere questo problema nel nostro ES5stesso, esaminiamolo prima di immergerci nelle funzioni della freccia ES6 su come risolverlo.

In genere, creare una variabile al di fuori della funzione interna del metodo. Ora il ‘forEach’metodo ottiene l'accesso thise quindi le object’sproprietà e i loro valori.

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 bindper allegare la thisparola chiave che fa riferimento al metodo al 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();

Ora con la ES6funzione freccia, possiamo affrontare il lexical scopingproblema in un modo più semplice.

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

Actor.showMovies();

Arrow functionssono più simili a istruzioni di funzioni, tranne che a bindquesto parent scope. Se l' argomento arrow function is in top scope, si thisfarà riferimento awindow/global scope , mentre una funzione freccia all'interno di una funzione regolare avrà questo argomento uguale alla sua funzione esterna.

Con le arrowfunzioni thisè associato alla chiusura scopeal momento della creazione e non può essere modificato. Il nuovo operatore, associa, chiama e applica non ha alcun effetto su questo.

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

Nell'esempio sopra, abbiamo perso il controllo di questo. Possiamo risolvere l'esempio sopra usando un riferimento variabile thiso usando bind. Con ES6, diventa più facile gestire thiscome legato 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 non per le funzioni freccia

All'interno di un oggetto letterale.

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

Actor.getName();

Actor.getNameè definito con una funzione freccia, ma al momento dell'invocazione avvisa indefinito perché this.namerimane undefinedil contestowindow .

Succede perché la funzione freccia lega il contesto in modo lessicale con l' window objectambito esterno. L'esecuzione this.nameè equivalente a window.name, che non è definito.

Prototipo di oggetto

La stessa regola si applica quando si definiscono i metodi su a prototype object. Invece di usare una funzione freccia per definire il metodo sayCatName, che porta un errore 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

Invocare i costruttori

thisin una chiamata di costruzione è l'oggetto appena creato. Quando si esegue nuova Fn (), nell'ambito della constructor Fntratta di un nuovo oggetto: this instanceof Fn === true.

this è impostato dal contesto allegato, ovvero l'ambito esterno che lo rende non assegnato all'oggetto appena creato.

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

Richiamata con contesto dinamico

La funzione freccia associa la contextdichiarazione staticamente attiva e non è possibile renderla dinamica. Collegare i listener di eventi agli elementi DOM è un'attività comune nella programmazione lato client. Un evento attiva la funzione del gestore con questo come elemento target.

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

thisè una finestra in una funzione freccia definita nel contesto globale. Quando si verifica un evento click, il browser tenta di richiamare la funzione del gestore con il contesto del pulsante, ma la funzione freccia non cambia il contesto predefinito. this.innerHTMLè equivalente window.innerHTMLe non ha senso.

Devi applicare un'espressione di funzione, che consente di cambiarla in base all'elemento target:

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

Quando l'utente fa clic sul pulsante, questo nella funzione del gestore è il pulsante. Quindi this.innerHTML = 'Clicked button'modifica correttamente il testo del pulsante per riflettere lo stato cliccato.

Riferimenti: https://dmitripavlutin.com/when-not-to-use-arrow-functions-in-javascript/


Bene, devo ammettere che "la migliore sta nel mezzo" . Votato per affermazione, che le funzioni freccia non copriranno eventuali casi d'uso delle funzioni. Sono davvero progettati per risolvere solo una parte dei problemi comuni. Passare completamente a loro sarà eccessivo.
BlitZ,

@DmitriPavlutin: controlla il mio post aggiornato, è una raccolta di molte cose ... potrebbe essere che dovrei pubblicare un riferimento.
Thalaivar,

2
Il codice dopo la riga "utilizzo di bind per associare questa parola chiave che fa riferimento al metodo alla funzione interna del metodo". contiene bug. Hai testato il resto dei tuoi esempi?
Isaac Pak,

Quello using bind to attach the this keyword that refers to the method to the method’s inner function.ha errori di sintassi.
Coda Chang,

Dovrebbe esserevar 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

Funzioni freccia: la funzionalità ES6 più utilizzata finora ...

Utilizzo: tutte le funzioni ES5 devono essere sostituite con le funzioni freccia ES6 ad eccezione dei seguenti scenari:

Le funzioni freccia NON devono essere utilizzate:

  1. Quando vogliamo il sollevamento di funzioni
    • poiché le funzioni freccia sono anonime.
  2. Quando vogliamo usare this/ argumentsin una funzione
    • poiché le funzioni freccia non hanno this/ argumentsproprie, dipendono dal loro contesto esterno.
  3. Quando vogliamo usare la funzione denominata
    • poiché le funzioni freccia sono anonime.
  4. Quando vogliamo usare la funzione come a constructor
    • poiché le funzioni freccia non hanno le proprie this.
  5. Quando vogliamo aggiungere la funzione come proprietà nell'oggetto letterale e usare l'oggetto in essa
    • come non possiamo accedere this(che dovrebbe essere oggetto stesso).

Cerchiamo di comprendere alcune delle varianti delle funzioni freccia per capire meglio:

Variante 1 : quando vogliamo passare più di un argomento a una funzione e restituire un valore da essa.

Versione ES5 :

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

Versione ES6 :

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

Nota: la functionparola chiave NON è obbligatoria. =>è obbligatorio. {}sono facoltativi, quando non forniamo {} returnviene aggiunto implicitamente da JavaScript e quando forniamo {}dobbiamo aggiungere returnse ne abbiamo bisogno.

Variante 2 : quando vogliamo passare SOLO un argomento a una funzione e restituire un valore da essa.

Versione ES5 :

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

Versione ES6 :

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

Nota: quando si passa solo un argomento, è possibile omettere la parentesi ().

Variante 3 : quando NON vogliamo passare alcun argomento a una funzione e NON vogliamo restituire alcun valore.

Versione ES5 :

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

Versione ES6 :

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

Variante 4 : quando vogliamo tornare esplicitamente dalle funzioni freccia.

Versione ES6 :

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

Variante 5 : quando vogliamo restituire un oggetto dalle funzioni freccia.

Versione ES6 :

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

Nota: dobbiamo racchiudere l'oggetto tra parentesi, ()altrimenti JavaScript non può distinguere tra un blocco e un oggetto.

Variante 6 : Le funzioni freccia NON hanno arguments(un array come un oggetto) proprie per cui dipendono dal contesto esterno arguments.

Versione ES6 :

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

Nota: fooè una funzione ES5, con un argumentsarray come oggetto e un argomento passato ad esso è 2quindi arguments[0]per fooè 2.

abcè una funzione di ES6 freccia Dal momento che non disponga di un proprio arguments, quindi, esso stampa arguments[0]del foocontesto esterno che è invece.

Variante 7 : Le funzioni delle frecce NON hanno thisle proprie, ma dipendono dal contesto esternothis

Versione 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: il callback passato a setTimeout è una funzione ES5 e ha una sua thische non è definita use-strictnell'ambiente, quindi otteniamo l'output:

undefined: Katty

Versione 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: il callback passato a setTimeoutè una funzione di freccia ES6 e NON ha la sua, thisquindi lo prende dal suo contesto esterno che è quello greetUserche ha quindi thisè obj6quindi ottenuto l'output:

Hi, Welcome: Katty

Varie: non è possibile utilizzare newcon le funzioni freccia. Le funzioni freccia non hanno prototypeproprietà. NON abbiamo associazione di thisquando la funzione freccia viene invocata tramite applyo call.


6

Oltre alle ottime risposte finora, vorrei presentare un motivo molto diverso per cui le funzioni freccia sono in un certo senso fondamentalmente migliori delle funzioni "normali" di JavaScript. Per motivi di discussione, supponiamo temporaneamente che utilizziamo un controllo di tipo come TypeScript o "Flow" di Facebook. Considera il seguente modulo giocattolo, che è un codice ECMAScript 6 valido più annotazioni sul tipo di flusso: (Includerò il codice non tipizzato, che sarebbe realisticamente derivato da Babel, alla fine di questa risposta, quindi può essere effettivamente eseguito).

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;};
  }
}

Ora vedi cosa succede quando usiamo la classe C da un modulo diverso, in questo modo:

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!

Come puoi vedere, il controllo del tipo non è riuscito qui: f2 avrebbe dovuto restituire un numero, ma ha restituito una stringa!

Peggio ancora, sembra che nessun verificatore di tipi concepibile sia in grado di gestire le normali funzioni JavaScript (senza freccia), poiché "questo" di f2 non si trova nell'elenco degli argomenti di f2, quindi non è possibile aggiungere il tipo richiesto per "questo" come annotazione per f2.

Questo problema riguarda anche le persone che non usano i controlli di tipo? Penso di sì, perché anche quando non abbiamo tipi statici, pensiamo come se fossero lì. ("I primi parametri devono essere un numero, il secondo una stringa" ecc.) Un "nascosto" argomento nascosto che può o non può essere usato nel corpo della funzione rende più difficile la nostra contabilità mentale.

Ecco la versione eseguibile non tipizzata, che sarebbe prodotta da 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!


4

Continuo a sostenere tutto ciò che ho scritto nella mia prima risposta in questa discussione. Tuttavia, la mia opinione sullo stile del codice si è sviluppata da allora, quindi ho una nuova risposta a questa domanda che si basa sulla mia ultima.

Per quanto riguarda lessicale this

Nella mia ultima risposta, ho deliberatamente evitato una convinzione di fondo su questa lingua, in quanto non era direttamente correlata all'argomento che stavo sostenendo. Tuttavia, senza che ciò sia esplicitamente dichiarato, posso capire perché molte persone si limitano a rinunciare alla mia raccomandazione di non usare le frecce, quando le trovano così utili.

La mia convinzione è questa: non dovremmo usare thisin primo luogo. Pertanto, se una persona evita deliberatamente l'uso thisnel suo codice, la funzione "lessicale this" delle frecce ha poco o nessun valore. Inoltre, con la premessa che thisè una cosa negativa, il trattamento di Arrow thisè meno di una "cosa buona;" invece, è più una forma di controllo del danno per un'altra caratteristica del linguaggio volgare.

Immagino che questo non accada ad alcune persone, ma anche a coloro a cui lo fa, devono inevitabilmente trovarsi a lavorare all'interno di codebase dove thisappare cento volte per file, e un po '(o molto) di controllo dei danni è tutto una persona ragionevole potrebbe sperare. Quindi le frecce possono essere buone, in un certo senso, quando migliorano una brutta situazione.

Anche se è più facile scrivere il codice con le thisfrecce piuttosto che senza di esse, le regole per l'uso delle frecce rimangono molto complesse (vedi: thread corrente). Pertanto, le linee guida non sono né "chiare" né "coerenti", come richiesto. Anche se i programmatori conoscono le ambiguità delle frecce, penso che si stringano nelle spalle e le accettano comunque, perché il valore del lessico le mette in thisombra.

Tutto ciò è una prefazione alla seguente realizzazione: se non si usa this, allora l'ambiguità thische causano quelle frecce normalmente diventa irrilevante. Le frecce diventano più neutrali in questo contesto.

Per quanto riguarda la sintassi concisa

Quando ho scritto la mia prima risposta, ero dell'opinione che persino l'adesione faziosa alle migliori pratiche fosse un prezzo utile da pagare se significasse che avrei potuto produrre un codice più perfetto. Ma alla fine mi sono reso conto che la terseness può servire come una forma di astrazione che può anche migliorare la qualità del codice - abbastanza da giustificare a volte allontanarsi dalle migliori pratiche.

In altre parole: dannazione, voglio anche le funzioni one-liner!

Per quanto riguarda una linea guida

Con la possibilità di thisfunzioni di freccia neutra e la terseness che vale la pena perseguire, offro le seguenti linee guida più indulgenti:

Linee guida per la notazione delle funzioni in ES6:

  • Non usare this.
  • Usa le dichiarazioni di funzione per le funzioni che chiameresti per nome (perché sono issate).
  • Utilizzare le funzioni freccia per i callback (perché tendono ad essere terser).

Concordare al 100% con la sezione "Linee guida per la notazione delle funzioni in ES6" nella parte inferiore, in particolare con le funzioni di sollevamento e richiamata in linea. bella risposta!
Jeff McCloud,


1

In modo semplice,

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

Un'altra istanza:

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();

Risp: la console stampa 20.

Il motivo è che ogni volta che viene eseguita una funzione viene creato il proprio stack, in questo esempio la exfunzione viene eseguita con l' newoperatore in modo da creare un contesto, e quando innerviene eseguita JS creerebbe un nuovo stack ed eseguirà la innerfunzione global contextsebbene ci sia un contesto locale.

Quindi, se vogliamo che la innerfunzione abbia un contesto locale che è exallora dobbiamo legare il contesto alla funzione interiore.

Le frecce risolvono questo problema, invece di prendere il Global contextprendono local contextse esiste uno qualsiasi. Nel given example,ci vorrà new ex()come this.

Quindi, in tutti i casi in cui l'associazione è esplicita, le frecce risolvono il problema per impostazione predefinita.


1

Le funzioni Arrow o Lambdas, sono state introdotte in ES 6. Oltre alla sua eleganza nella sintassi minima, la differenza funzionale più notevole è lo scoping this all'interno di una funzione freccia

Nelle espressioni di funzioni regolari , la thisparola chiave è associata a valori diversi in base al contesto in cui viene chiamata.

Nelle funzioni freccia , thisè associato in modo lessicale , il che significa che si chiude thisdall'ambito in cui è stata definita la funzione freccia (ambito padre) e non cambia indipendentemente da dove e come viene invocato / chiamato.

Limitazioni Freccia-Funzioni come metodi su un oggetto

// 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]

Nel caso in objA.print()cui il print()metodo è stato definito usando il metodo regolare function , ha funzionato risolvendosi thiscorrettamente objAper l'invocazione del metodo ma non è riuscito quando è stato definito come una =>funzione freccia . È perché thisin una normale funzione quando viene invocato come metodo su un oggetto ( objA), è l'oggetto stesso. Tuttavia, nel caso di una funzione freccia, thisviene legato in modo lessicale al campo thisdi applicazione racchiuso nel punto in cui è stato definito (globale / Finestra nel nostro caso) e rimane tale durante la sua chiamata come metodo attivo objA.

Vantaggi delle funzioni di una freccia rispetto alle normali funzioni nei metodi di un oggetto MA solo quando thissi prevede che siano fissati e associati alla definizione temporale.

/* 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]

Nel caso in objB.print()cui il print()metodo sia definito come funzione che invoca console.log([$ {this.id} -> {this.name}] in )modo asincrono come richiamata setTimeout , thisrisolto correttamente objBquando una funzione freccia è stata utilizzata come richiamata ma non è riuscita quando la richiamata è stata definita come funzione regolare. È perché la =>funzione freccia è passata alla setTimeout(()=>..)chiusura thislessicale dal suo genitore cioè. invocazione di objB.print()cui lo ha definito. In altre parole-, la freccia =>funzione passata a per setTimeout(()==>...legato a objBcome thiscausa della a invocazione objB.print() thisera objBstessa.

Potremmo facilmente usare Function.prototype.bind(), per far funzionare il callback come una normale funzione, vincolandolo al corretto 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]

Tuttavia, le funzioni freccia sono utili e meno soggette a errori nel caso di richiami asincroni in cui conosciamo thisil momento della definizione delle funzioni a cui arriva e dovrebbe essere associato.

Limitazione delle funzioni freccia in cui ciò deve cambiare tra le invocazioni

In qualsiasi momento, abbiamo bisogno di una funzione che thispuò essere modificata al momento dell'invocazione, non possiamo usare le funzioni freccia.

/* 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]

Nessuna delle const print = () => { console.log(opzioni precedenti funzionerà con la funzione freccia [$ {this.id} -> {this.name}] in );}quanto thisnon può essere modificata e rimarrà vincolata thisall'ambito di applicazione in cui è stata definita (globale / Finestra). In tutti questi esempi, abbiamo invocato la stessa funzione con oggetti diversi ( obj1e obj2) uno dopo l'altro, entrambi creati dopo la print()dichiarazione della funzione.

Questi erano esempi inventati, ma pensiamo ad alcuni esempi di vita reale. Se dovessimo scrivere il nostro reduce()metodo in modo simile a quello su cui funziona arrays , non potremmo ancora definirlo come lambda, perché deve inferire thisdal contesto di invocazione, cioè. l'array su cui è stato invocato

Per questo motivo, le constructorfunzioni non possono mai essere definite come funzioni freccia, poiché thisper una funzione di costruzione non è possibile impostare al momento della sua dichiarazione. Ogni volta che una funzione di costruzione viene invocata con una newparola chiave, viene creato un nuovo oggetto che viene quindi associato a quella particolare chiamata.

Inoltre, quando i framework o i sistemi accettano una o più funzioni di callback da invocare in un secondo momento con un contesto dinamico this , non possiamo usare le funzioni freccia poiché thispotrebbe essere necessario cambiare nuovamente ad ogni invocazione. Questa situazione generalmente si risolve con i gestori di eventi 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');
});

Questo è anche il motivo per cui in framework come Angular 2+ e Vue.js si aspettano che i metodi di associazione dei componenti del modello siano funzioni / metodi regolari poiché la thisloro invocazione è gestita dai framework per le funzioni di associazione. (Angular utilizza Zone.js per gestire il contesto asincrono per le invocazioni delle funzioni di associazione del modello di visualizzazione).

D'altra parte, in React , quando vogliamo passare il metodo di un componente come un gestore di eventi, ad esempio <input onChange={this.handleOnchange} />, dovremmo definire handleOnchanage = (event)=> {this.props.onInputChange(event.target.value);}una funzione freccia come per ogni invocazione, vogliamo che questa sia la stessa istanza del componente che ha prodotto il JSX per il rendering Elemento DOM.


Questo articolo è disponibile anche sulla mia pubblicazione Medium . Se ti piace l'artile o hai commenti e suggerimenti, applaudi o lascia commenti su Medium .

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.