tutorial - javascript promise




Quando dovrei usare le funzioni Arrow in ECMAScript 6? (5)

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 si distinguono spesso per essere anonime, ma in ECMAScript qualsiasi funzione può essere anonima. Ciascuno dei due tipi ha domini di utilizzo univoci (vale a dire quando è necessario vincolare esplicitamente o esplicitamente il proprio limite). Tra questi domini vi è un vasto numero di casi in cui entrambe le notazioni lo faranno.

Le funzioni freccia in ES6 hanno almeno due limitazioni:

  • Non lavorare con new
  • Risolto this limite allo scope all'inizializzazione

A parte queste due limitazioni, le funzioni delle frecce potrebbero teoricamente sostituire le funzioni regolari quasi ovunque. Qual è l'approccio giusto per usarli nella pratica? Dovrebbero essere utilizzate le funzioni di freccia ad esempio:

  • "ovunque lavorano", cioè ovunque una funzione non deve essere agnostica riguardo a this variabile e non stiamo creando un oggetto.
  • solo "ovunque sono necessari", ovvero ascoltatori di eventi, timeout, che devono essere associati a un determinato ambito
  • con funzioni "corte" ma non con funzioni "lunghe"
  • solo con funzioni che non contengono un'altra funzione freccia

Quello che sto cercando è una linea guida per selezionare la notazione della funzione appropriata nella versione futura di ECMAScript. La linea guida dovrà essere chiara, in modo che possa essere insegnata agli sviluppatori in una squadra e essere coerente in modo che non richieda un costante refactoring avanti e indietro da una notazione di funzioni a un'altra.


Funzioni della freccia: la funzione ES6 più utilizzata finora ...

Utilizzo: tutte le funzioni ES5 devono essere sostituite con le funzioni freccia ES6, tranne nei seguenti scenari:

Le funzioni della freccia NON devono essere utilizzate:

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

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

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

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 parola chiave function NON è richiesta. => è richiesto. {} sono facoltativi, quando non forniamo {} return è implicitamente aggiunto da JavaScript e quando forniamo {} dobbiamo aggiungere il reso se ne abbiamo bisogno.

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

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 della freccia.

Versione ES6 :

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

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

Versione ES6 :

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

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

Variante 6 : Le funzioni delle frecce NON hanno arguments (un oggetto come un array) da cui dipendono dal contesto esterno per gli arguments .

Versione ES6 :

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

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

abc è una funzione di freccia ES6 poiché NON ha i propri arguments quindi stampa gli arguments[0] di foo è invece il contesto esterno.

Variante 7 : Le funzioni di freccia NON hanno this da sole dipendono dal contesto esterno per this

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 il suo valore non definito nell'ambiente use-strict , quindi otteniamo 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 freccia ES6 e NON ha il proprio valore così da prendere il suo contesto esterno che è greetUser che ha this che è obj6 quindi otteniamo output:

Hi, Welcome: Katty

Varie: non possiamo usare newcon le funzioni freccia. Le funzioni freccia non hanno prototypeproprietà. NON abbiamo alcun legame con thisquando la funzione freccia viene invocata tramite applyo call.


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 la function nell'ambito globale e per Object.prototype proprietà Object.prototype .
  • Utilizzare la class per i costruttori di oggetti.
  • Usa => ovunque.

Perché utilizzare le funzioni della freccia quasi ovunque?

  1. Sicurezza dell'oscilloscopio: quando le funzioni di freccia vengono utilizzate in modo coerente, è garantito l'utilizzo dello stesso thisObject come root. Se anche una singola funzione standard di callback è combinata con un gruppo di funzioni di una freccia, c'è la possibilità che l'oscilloscopio si incasini.
  2. Compattezza: le funzioni delle frecce sono più facili da leggere e scrivere. (Questo può sembrare supponente, quindi darò qualche esempio in seguito).
  3. Chiarezza: quando quasi tutto è una funzione di freccia, qualsiasi function regolare si distingue immediatamente per definire l'ambito. Uno sviluppatore può sempre cercare l'istruzione della function successiva più alta per vedere che cos'è thisObject .

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'oggetto window (ambito globale) è meglio indirizzato in modo esplicito.
  3. Molte definizioni di Object.prototype risiedono nell'ambito globale (think String.prototype.truncate ecc.) E in genere devono essere di tipo type. L'uso costante della function nell'ambito globale aiuta a evitare errori.
  4. Molte funzioni nell'ambito globale sono costruttori di oggetti per le definizioni di classi vecchio stile.
  5. Le funzioni possono essere nominate 1 . Questo ha due vantaggi: (1) È meno difficile scrivere la function foo(){} di const foo = () => {} - in particolare al di fuori delle 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 delle funzioni vengono hoisted , (nel senso che è possibile accedervi prima che vengano dichiarate), che è un utile attributo 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 di freccia è quindi che le funzioni sono doppie come costruttori di oggetti:

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

Tuttavia, la definizione della classe di draft 2 ES Harmony identica dal punto di vista funzionale è quasi altrettanto compatta:

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

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

Laddove è necessario un costruttore di oggetti, si dovrebbe considerare di convertire la funzione in una class come mostrato sopra. La sintassi funziona anche con funzioni / classi anonime.


Leggibilità delle funzioni di freccia

Probabilmente il miglior argomento per attenersi a funzioni regolari - la sicurezza dell'oscilloscopio è dannato - sarebbe che le funzioni delle frecce sono meno leggibili rispetto alle normali funzioni. Se il tuo codice non è funzionale in primo luogo, allora le funzioni di freccia potrebbero non sembrare necessarie, e quando le funzioni di freccia non vengono utilizzate in modo coerente appaiono brutte.

ECMAScript è cambiato parecchio da quando ECMAScript 5.1 ci ha fornito Array.forEach , Array.map e tutte queste funzionalità di programmazione funzionale che ci hanno utilizzato funzioni in cui for-loops sarebbe stato usato prima. JavaScript asincrono è decollato parecchio. ES6 invierà anche un oggetto Promise , il che significa ancora più funzioni anonime. Non si può tornare indietro per la programmazione funzionale. Nel JavaScript funzionale, le funzioni freccia sono preferibili rispetto alle normali funzioni.

Prendiamo ad esempio questo pezzo (particolarmente confuso) del 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 delle frecce può essere sostituita da una funzione standard, ci sarebbe ben poco da guadagnare dal farlo. Quale versione è più leggibile? Direi il primo.

Penso che la domanda se utilizzare le funzioni di freccia o le funzioni regolari diventerà meno rilevante nel tempo. La maggior parte delle funzioni diventeranno metodi di classe, che elimineranno la parola chiave function , o diventeranno classi. Le funzioni rimarranno in uso per il patching delle classi tramite Object.prototype . Nel frattempo suggerisco di riservare la parola chiave function per tutto ciò che dovrebbe essere un metodo di classe o una classe.

Gli appunti

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

Le funzioni freccia sono state create per semplificare l' scope funzioni e risolvere la parola chiave rendendola più semplice. Utilizzano la sintassi => , che assomiglia a una freccia.

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

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

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

Lo snippet sopra riportato si riferirebbe ad un object e stamperebbe il nome "RajiniKanth" . Esploriamo il frammento di sotto e vediamo cosa vorrebbe far notare 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 parola chiave fosse all'interno della method's function del method's function ?

Qui questo si riferirebbe window object piuttosto che alla inner function come se fosse fuori scope . Poiché this , fa sempre riferimento al proprietario della funzione in cui si trova, in questo caso, poiché ora è fuori ambito, la finestra / oggetto globale.

Quando si trova all'interno del metodo di un object , il proprietario della function è l'oggetto. Quindi questa parola chiave è legata all'oggetto. Tuttavia, quando si trova all'interno di una funzione, stand alone o all'interno di un altro metodo, farà sempre riferimento alla window/global oggetto window/global .

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

fn(); // [object Window]

Ci sono modi per risolvere questo problema nel nostro ES5 stesso, esaminiamolo prima di immergerci nelle funzioni delle frecce ES6 su come risolverlo.

In genere si dovrebbe creare una variabile al di fuori della funzione interna del metodo. Ora il metodo 'forEach' ottiene l'accesso a this e quindi alle proprietà object's e ai 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();

utilizzando bind per collegare la parola chiave che fa riferimento al metodo alla 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 funzione di freccia ES6 , possiamo affrontare il problema dello lexical scoping 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 functions sono più simili alle istruzioni di funzione, tranne per il fatto che bind questo parent scope . Se la arrow function is in top scope , this argomento farà riferimento a window/global scope , mentre una funzione freccia all'interno di una funzione regolare avrà questo argomento uguale alla sua funzione esterna.

Con le funzioni a arrow this è legato allo scope racchiude al momento della creazione e non può essere modificato. Il nuovo operatore, bind, call e apply non hanno 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 precedente usando un riferimento variabile di this o usando bind . Con ES6, diventa più semplice gestire this come legato allo 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 alle funzioni di 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 su invocazione avvisa indefinito perché this.name undefined è undefined quanto il contesto rimane alla window .

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

Prototipo dell'oggetto

La stessa regola si applica quando si definiscono metodi su un prototype object . Invece di usare una funzione freccia per definire il metodo sayCatName, che porta una context window errata:

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 costruttori

this in un'invocazione di costruzione è l'oggetto appena creato. Quando si esegue il nuovo Fn (), il contesto del constructor Fn è un nuovo oggetto: this instanceof Fn === true .

this è l'impostazione dal contesto che la racchiude, cioè l'ambito esterno che la rende non assegnata 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 lega staticamente il context alla dichiarazione e non è possibile renderla dinamica. Allegare listener di eventi agli elementi DOM è un compito comune nella programmazione lato client. Un evento attiva la funzione di gestione con questo come elemento di destinazione.

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

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

Devi applicare un'espressione di funzione, che consente di modificarlo a seconda dell'elemento di destinazione:

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 gestore è il pulsante. Quindi this.innerHTML = 'Clicked button' modifica correttamente il testo del pulsante per riflettere lo stato cliccato.

Riferimenti: https://rainsoft.io/when-not-to-use-arrow-functions-in-javascript/


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

Risposta: la console stamperebbe 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 eseguito, JS creerebbe un nuovo stack ed eseguirà la innerfunzione a global contextsebbene esista un contesto locale.

Quindi, se vogliamo che la innerfunzione abbia un contesto locale, è exnecessario legare il contesto alla funzione interna.

Le frecce risolvono questo problema, invece di prendere il Global contextloro prendono il local contextse esiste. Nel given example,prenderà new ex()come this.

Quindi, in tutti i casi in cui l'associazione è esplicita, le frecce risolvono il problema in base alle impostazioni predefinite.






arrow-functions