Descoperiți JavaScript funcțional a fost numit unul dintre cele mai bune cărți noi de programare funcțională de BookAuthority!

ES6 aduce mai multe funcții în limba JavaScript. Unele sintaxe noi vă permit să scrieți codul într-un mod mai expresiv, unele caracteristici completează setul de instrumente funcționale de programare, iar unele caracteristici sunt discutabile.

lasa si const

Există două modalități de declarare a unei variabile (let și const) plus unul care a devenit depășit (var).

lăsa

let declară și opțional inițializează o variabilă în domeniul de aplicare curent. Domeniul actual poate fi fie un modul, o funcție sau un bloc. Valoarea unei variabile care nu este inițializată este undefined .

Domeniul de aplicare definește durata de viață și vizibilitatea unei variabile. Variabilele nu sunt vizibile în afara domeniului în care sunt declarate.

Luați în considerare următorul cod care subliniază let blocare domeniu:

let x = 1;
{ 
  let x = 2;
}
console.log(x); //1

În schimb, var declarația nu avea un domeniu de aplicare bloc:

var x = 1;
{ 
  var x = 2;
}
console.log(x); //2

for declarație buclă, cu let declarație, creează o nouă variabilă locală pentru domeniul de aplicare al blocului, pentru fiecare iterație. Următoarea buclă creează cinci închideri peste cinci diferite i variabile.

(function run(){
  for(let i=0; i<5; i++){
    setTimeout(function log(){
      console.log(i); //0 1 2 3 4
    }, 100);
  }
})();

Scrierea aceluiași cod cu var va crea cinci închideri, peste aceeași variabilă, astfel încât toate închiderile vor afișa ultima valoare de i.

log() funcția este o închidere. Pentru mai multe despre închideri, aruncați o privire la Descoperiți puterea închiderilor în JavaScript.

const

const declară o variabilă care nu poate fi realocată. Devine o constantă numai atunci când valoarea atribuită este imuabilă.

O valoare imuabilă este o valoare care, odată creată, nu poate fi modificată. Valorile primitive sunt imuabile, obiectele sunt mutabile.

const blochează variabila, Object.freeze() îngheață obiectul.

Inițializarea const variabila este obligatorie.

Module

Înainte de module, o variabilă declarată în afara oricărei funcții era o variabilă globală.

Cu modulele, o variabilă declarată în afara oricărei funcții este ascunsă și nu este disponibilă pentru alte module decât dacă este exportată în mod explicit.

Exportul face o funcție sau un obiect disponibil pentru alte module. În exemplul următor, export funcții din diferite module:

//module "./TodoStore.js"
export default function TodoStore(){}

//module "./UserStore.js"
export default function UserStore(){}

Importarea face ca o funcție sau un obiect, din alte module, să fie disponibil pentru modulul curent.

import TodoStore from "./TodoStore";
import UserStore from "./UserStore";

const todoStore = TodoStore();
const userStore = UserStore();

Intinde / Odihnește

operatorul poate fi operatorul spread sau parametrul rest, în funcție de locul în care este utilizat. Luați în considerare următorul exemplu:

const numbers = [1, 2, 3];
const arr = ['a', 'b', 'c', ...numbers];

console.log(arr);
["a", "b", "c", 1, 2, 3]

Acesta este operatorul spread. Uită-te acum la următorul exemplu:

function process(x,y, ...arr){
  console.log(arr)
}
process(1,2,3,4,5);
//[3, 4, 5]

function processArray(...arr){
  console.log(arr)
}
processArray(1,2,3,4,5);
//[1, 2, 3, 4, 5]

Acesta este parametrul rest.

argumente

Cu parametrul rest putem înlocui arguments pseudo-parametru. Parametrul rest este o matrice, arguments nu este.

function addNumber(total, value){
  return total + value;
}

function sum(...args){
  return args.reduce(addNumber, 0);
}

sum(1,2,3); //6

Clonarea

Operatorul de răspândire face clonarea obiectelor și a matricelor mai simplă și mai expresivă.

Operatorul de proprietăți de răspândire a obiectelor va fi disponibil ca parte a ES2018.

const book = { title: "JavaScript: The Good Parts" };

//clone with Object.assign()
const clone = Object.assign({}, book);

//clone with spread operator
const clone = { ...book };

const arr = [1, 2 ,3];

//clone with slice
const cloneArr = arr.slice();

//clone with spread operator
const cloneArr = [ ...arr ];

Concatenare

În exemplul următor, operatorul de răspândire este utilizat pentru concatenarea matricelor:

const part1 = [1, 2, 3];
const part2 = [4, 5, 6];

const arr = part1.concat(part2);

const arr = [...part1, ...part2];

Fuzionarea obiectelor

Operatorul de răspândire, cum ar fi Object.assign(), poate fi folosit pentru a copia proprietăți de la unul sau mai multe obiecte la un obiect gol și a combina proprietățile acestora.

const authorGateway = { 
  getAuthors : function() {},
  editAuthor: function() {}
};

const bookGateway = { 
  getBooks : function() {},
  editBook: function() {}
};

//copy with Object.assign()
const gateway = Object.assign({},
      authorGateway, 
      bookGateway);
      
//copy with spread operator
const gateway = {
   ...authorGateway,
   ...bookGateway
};

Proprietate cu mâini scurte

Luați în considerare următorul cod:

function BookGateway(){
  function getBooks() {}
  function editBook() {}
  
  return {
    getBooks: getBooks,
    editBook: editBook
  }
}

Cu proprietățile scurte ale proprietății, când numele proprietății și numele variabilei utilizate ca valoare sunt aceleași, putem scrie cheia o singură dată.

function BookGateway(){
  function getBooks() {}
  function editBook() {}
  
  return {
    getBooks,
    editBook
  }
}

Iată un alt exemplu:

const todoStore = TodoStore();
const userStore = UserStore();
    
const stores = {
  todoStore,
  userStore
};

Alocarea destructurării

Luați în considerare următorul cod:

function TodoStore(args){
  const helper = args.helper;
  const dataAccess = args.dataAccess;
  const userStore = args.userStore;
}

Cu sintaxa de alocare a destructurării, se poate scrie astfel:

function TodoStore(args){
   const { 
      helper, 
      dataAccess, 
      userStore } = args;
}

sau chiar mai bine, cu sintaxa de destructurare în lista de parametri:

function TodoStore({ helper, dataAccess, userStore }){}

Mai jos este apelul funcțional:

TodoStore({ 
  helper: {}, 
  dataAccess: {}, 
  userStore: {} 
});

Parametri impliciti

Funcțiile pot avea parametri impliciți. Uită-te la următorul exemplu:

function log(message, mode = "Info"){
  console.log(mode + ": " + message);
}

log("An info");
//Info: An info

log("An error", "Error");
//Error: An error

Litere șir șablon

Șirurile șablon sunt definite cu ` caracter. Cu șirurile șablon, mesajul de înregistrare anterior poate fi scris astfel:

function log(message, mode= "Info"){
  console.log(`${mode}: ${message}`);
}

Șirurile șablonului pot fi definite pe mai multe linii. Cu toate acestea, o opțiune mai bună este păstrarea mesajelor text lungi ca resurse, de exemplu într-o bază de date.

Vedeți mai jos o funcție care generează un cod HTML care acoperă mai multe linii:

function createTodoItemHtml(todo){
  return `<li>
    <div>${todo.title}</div>
    <div>${todo.userName}</div>
  </li>`;
}

Apeluri corespunzătoare

O funcție recursivă este recursivă după ce apelul recursiv este ultimul lucru pe care îl face funcția.

Funcțiile recursive ale cozii au performanțe mai bune decât funcțiile recursive non-coadă. Apelul recursiv de coadă optimizat nu creează un nou cadru de stivă pentru fiecare apel de funcție, ci folosește mai degrabă un singur cadru de stivă.

ES6 aduce optimizarea apelului în mod strict.

Următoarea funcție ar trebui să beneficieze de optimizarea apelului.

function print(from, to) 
{ 
  const n = from;
  if (n > to)  return;
  
  console.log(n);
    
  //the last statement is the recursive call 
  print(n + 1, to); 
}

print(1, 10);

Notă: optimizarea apelurilor de bază nu este încă acceptată de browserele majore.

Promisiuni

O promisiune este o referință la un apel asincron. Se poate rezolva sau eșua undeva în viitor.

Promisiunile sunt mai ușor de combinat. După cum vedeți în exemplul următor, este ușor să apelați o funcție atunci când toate promisiunile sunt rezolvate sau când prima promisiune este rezolvată.

function getTodos() { return fetch("/todos"); }
function getUsers() { return fetch("/users"); }
function getAlbums(){ return fetch("/albums"); }

const getPromises = [
  getTodos(), 
  getUsers(), 
  getAlbums()
];

Promise.all(getPromises).then(doSomethingWhenAll);
Promise.race(getPromises).then(doSomethingWhenOne);

function doSomethingWhenAll(){}
function doSomethingWhenOne(){}

fetch() funcția, care face parte din API-ul Fetch, returnează o promisiune.

Promise.all() returnează o promisiune care se rezolvă atunci când toate promisiunile de intrare s-au rezolvat. Promise.race() returnează o promisiune care rezolvă sau respinge atunci când una dintre promisiuni intră rezolvă sau respinge.

O promisiune poate fi în una dintre cele trei stări: în așteptare, rezolvată sau respinsă. Promisiunea va rămâne în așteptare până când este fie rezolvată, fie respinsă.

Promisiunile acceptă un sistem de înlănțuire care vă permite să transmiteți datele printr-un set de funcții. În exemplul următor, rezultatul getTodos() este transmis ca intrare la toJson(), apoi rezultatul său este transmis ca intrare la getTopPriority(), și apoi rezultatul său este transmis ca intrare la renderTodos() funcţie. Când o eroare este aruncată sau o promisiune este respinsă handleError se numește.

getTodos()
  .then(toJson)
  .then(getTopPriority)
  .then(renderTodos)
  .catch(handleError);

function toJson(response){}
function getTopPriority(todos){}
function renderTodos(todos){}
function handleError(error){}

În exemplul anterior, .then() se ocupă de scenariul de succes și .catch() gestionează scenariul de eroare. Dacă există o eroare la orice pas, controlul lanțului sare la cel mai apropiat manipulator de respingere pe lanț.

Promise.resolve() returnează o promisiune rezolvată. Promise.reject() returnează o promisiune respinsă.

Clasă

Clasa este sintaxa zahărului pentru crearea obiectelor cu un prototip personalizat. Are o sintaxă mai bună decât cea precedentă, constructorul de funcții. Consultați următorul exemplu:

class Service {
  doSomething(){ console.log("doSomething"); }
}

let service = new Service();
console.log(service.__proto__ === Service.prototype);

Toate metodele definite în Service clasa va fi adăugată laService.prototype obiect. Exemple de Service clasa va avea același prototip (Service.prototype) obiect. Toate instanțele vor delega apeluri de metodă către Service.prototype obiect. Metodele sunt definite o datăService.prototype și apoi moștenit de toate cazurile.

Moştenire

„Clasele pot moșteni din alte clase”. Mai jos este un exemplu de moștenireunde SpecialService clasa „moștenește” de la Service clasă:

class Service {
  doSomething(){ console.log("doSomething"); }
}

class SpecialService extends Service {
  doSomethingElse(){ console.log("doSomethingElse"); }  
}

let specialService = new SpecialService();
specialService.doSomething();
specialService.doSomethingElse();

Toate metodele definite în SpecialService clasa va fi adăugată la SpecialService.prototype obiect. Toate instanțele vor delega apeluri de metodă către SpecialService.prototype obiect. Dacă metoda nu se găsește în SpecialService.prototype, va fi căutat în Service.prototypeobiect. Dacă încă nu este găsit, va fi căutat în Object.prototype.

Clasa poate deveni o caracteristică proastă

Chiar dacă par încapsulate, toți membrii unei clase sunt publice. Încă trebuie să gestionați problemele this pierzând contextul. API-ul public este modificabil.

class poate deveni o caracteristică proastă dacă neglijați partea funcțională a JavaScript-ului. class poate da impresia unui limbaj bazat pe clase atunci când JavaScript este atât un limbaj de programare funcțional, cât și un limbaj bazat pe prototip.

Obiectele încapsulate pot fi create cu funcții din fabrică. Luați în considerare următorul exemplu:

function Service() {
  function doSomething(){ console.log("doSomething"); }
  
  return Object.freeze({
     doSomething
  });
}

De această dată, toți membrii sunt privați în mod implicit. API-ul public este imuabil. Nu este nevoie să gestionați problemele cu this pierzând contextul.

class poate fi folosit ca o excepție dacă este cerut de cadrul componentelor. Acesta a fost cazul cu React, dar nu mai este cazul React Hooks.

Pentru mai multe informații despre de ce să favorizați funcțiile din fabrică, aruncați o privire Funcția clasă vs fabrică: explorarea drumului de urmat.

Funcții săgeată

Funcțiile săgeți pot crea funcții anonime din mers. Acestea pot fi utilizate pentru a crea apeluri de apel mici, cu o sintaxă mai scurtă.

Să luăm o colecție de sarcini. Un lucru de făcut are un id , A title , și a completed proprietate booleană. Acum, ia în considerare următorul cod care selectează numai fișierul title din colecție:

const titles = todos.map(todo => todo.title);

sau următorul exemplu selectând numai todos care nu sunt finalizate:

const filteredTodos = todos.filter(todo => !todo.completed);

acest

Funcțiile săgeată nu au propriile lor funcții this și arguments. Ca urmare, este posibil să vedeți funcția săgeată utilizată pentru a remedia problemele this pierzând contextul. Cred că cel mai bun mod de a evita această problemă este să nu folosiți this deloc.

Funcțiile săgeții pot deveni o caracteristică proastă

Funcțiile săgeți pot deveni o caracteristică proastă atunci când sunt utilizate în detrimentul funcțiilor numite. Acest lucru va crea probleme de lizibilitate și întreținere. Uită-te la următorul cod scris numai cu funcții săgeată anonime:

const newTodos = todos.filter(todo => 
       !todo.completed && todo.type === "RE")
    .map(todo => ({
       title : todo.title,
       userName : users[todo.userId].name
    }))
    .sort((todo1, todo2) =>  
      todo1.userName.localeCompare(todo2.userName));

Acum, verificați aceeași logică refactorizat la funcții pure cu intenția de a dezvălui nume și decide care dintre ele este mai ușor de înțeles:

const newTodos = todos.filter(isTopPriority)
  .map(partial(toTodoView, users))
  .sort(ascByUserName);

function isTopPriority(todo){
  return !todo.completed && todo.type === "RE";
}
  
function toTodoView(users, todo){
  return {
    title : todo.title,
    userName : users[todo.userId].name
  }
}

function ascByUserName(todo1, todo2){
  return todo1.userName.localeCompare(todo2.userName);
}

Și mai mult, funcțiile săgeată anonime vor apărea ca (anonymous) în pila de apeluri.

Pentru mai multe informații despre de ce să favorizați funcțiile numite, aruncați o privire la Cum să vă îmbunătățiți codul cu nume de funcții care dezvăluie intenția.

Mai puțin cod nu înseamnă necesar mai ușor de citit. Uită-te la următorul exempluși vedeți ce versiune vă este mai ușor de înțeles:

//with arrow function
const prop = key => obj => obj[key];

//with function keyword
function prop(key){
   return function(obj){
      return obj[key];
   }
}

Acordați atenție la returnarea unui obiect. În exemplul următor, getSampleTodo() se intoarce undefined.

const getSampleTodo = () => { title : "A sample todo" };

getSampleTodo();
//undefined

Generatoare

Cred că generatorul ES6 este o caracteristică inutilă care face codul mai complicat.

Generatorul ES6 creează un obiect care are next() metodă. next() metoda creează un obiect care are value proprietate. Generatoarele ES6 promovează utilizarea buclelor. Aruncați o privire la codul de mai jos:

function* sequence(){
  let count = 0;
  while(true) {
    count += 1;
    yield count;
  }
}

const generator = sequence();
generator.next().value;//1
generator.next().value;//2
generator.next().value;//3

Același generator poate fi implementat simplu cu o închidere.

function sequence(){
  let count = 0;
  return function(){
    count += 1;
    return count;
  }
}

const generator = sequence();
generator();//1
generator();//2
generator();//3

Pentru mai multe exemple cu generatoare funcționale, aruncați o privire Să experimentăm cu generatoare funcționale și cu operatorul de conducte în JavaScript.

Concluzie

let și const declarați și inițializați variabile.

Modulele încapsulează funcționalitatea și expun doar o mică parte.

Operatorul spread, parametrul rest și stenograma proprietății fac lucrurile mai ușor de exprimat.

Promisiunile și recursivitatea cozii completează setul de instrumente funcționale de programare.

Descoperiți JavaScript funcțional a fost numit unul dintre cele mai bune cărți noi de programare funcțională de BookAuthority!

Pentru mai multe despre aplicarea tehnicilor de programare funcționale în React, aruncați o privire Reactie functionala.

Învăța funcțional React, într-un mod bazat pe proiecte, cu Arhitectură funcțională cu React și Redux.

Urmăriți pe Twitter