JavaScript ES6 - Functiile Arrow

Un nou mod de definire a functiilor in ES6

Daniel Turcu

5 minute read

Avem o noua modalitate de definire a functiilor in JavaScript (ES6): Functiile Arrow (Arrow Functions sau Fat Arrow Functions cum li se mai spune). Sunt functii anonime, utilizate in special ca one-linere, de obicei trimise ca parametri altor functii.

Nu doar sintaxa difera, fata de functiile clasice; Cele doua tipuri de functii primesc valori diferite pentru this (atunci cand functia este executata in alt context decat cel in care a fost definita).


Sintaxa

Sa pornim de la o functie standard si sa o transformam intr-o functie arrow:

function double(value) {
    return 2 * value;
}

Echivalentul functiei de mai sus in sintaxa arrow este:

    const double = (value) => {
        return 2 * value;
    }

Putem spune ca nu am obtinut mare lucru. Ba chiar am scris cateva caractere in plus. Dar functia de mai sus are un singur parametru, asa ca putem sa o rescriem asa:

const double = value => {
    return 2 * value;
}

Mai departe, daca intreg corpul functiei il putem rescrie ca o singura instructiune putem sa mai taiem cate ceva:

const double = value => 2 * value;

O functie fara parametri poate fi scrisa astfel:

const printMessage = () => 'Static Message!';

Mai putem sa scurtam definitia functiei printMessage?

const printMessage = _ => 'Static Message!';
  • Underscore ( _ ) poate fi nume de variabila, asa ca am definit functia cu un singur parametru, care, insa, nu este utilizat in cod. Nu recomand totusi aceasta sintaxa, desi o sa o mai intalniti din cand in cand.

this

Marele avantaj al functiilor arrow, este faptul ca pastreaza valoarea lui this din contextul de definitie, nu se mai modifica in functie de contextul de executie.

Sa pornim de la urmatorul exemplu:


let task = {
    subject: 'New bug found',
    status: 'New',
    rejectedReason: '',
    close: function(reason, logFn) {
        this.rejectedReason = reason || '';
        this.status = 'Closed';
        if (typeof logFn === 'function') 
            logFn(reason);
    },
    reject: function(reason) {
        this.close(reason, function(reason) {
            console.log('Task: ' + this.subject + ' rejected for reason: ' + this.rejectedReason);
        })
    }
}

task.close();
console.log(task);

  • avem o functie care inchide task-ul
  • optional poate primi un motiv pentru inchiderea task-ului
  • logFn este functie callback optionala, pentru afisarea unui mesaj in consola

Rezultatul, in consola, al codului de mai sus este:

{subject: "New bug found", status: "Closed", rejectedReason: ""}

Mai jos incercam sa rulam functia task.reject():

task.reject('Not applicable');
// Task: undefined rejected for reason: undefined
  • this.subject si this.rejectedReason au valoarea undefined, deoarece contextul de executie este altul decat cel asteptat de functia reject. Schimbarea s-a produs cand am trimis functia logFn ca parametru.

In ES5 rezolvam problema, cel mai adesea, prin captarea referintei this de la momentul definitiei sau prin fixarea contextului de executie folosind bind():


let task = {
    subject: 'New bug found',
    status: 'New',
    rejectedReason: '',
    close: function(reason, logFn) {
        this.rejectedReason = reason || '';
        this.status = 'Closed';
        if (typeof logFn === 'function') 
            logFn(reason);
    },
    reject: function(reason) {
        let _this = this;    // capture reference of this
        this.close(reason, function(reason) {
            console.log('Task: ' + _this.subject + ' rejected for reason: ' + _this.rejectedReason);    // use _this instead
        })
    }
}

task.reject('Not applicable');
// Task: New bug found rejected for reason: Not applicable

A doua solutie ar fi sa utilizam bind()

  • bind returneaza o noua functie care are contextul de executie fixat. Nu se mai poate modifica nici daca este trimisa spre executie in alt context. (de obicei cand este invocata asincronic in urma unui eveniment, API request, etc…)

let task = {
    subject: 'New bug found',
    status: 'New',
    rejectedReason: '',
    close: function(reason, logFn) {
        this.rejectedReason = reason || '';
        this.status = 'Closed';
        if (typeof logFn === 'function') 
            logFn(reason);
    },
    reject: function(reason) {

        let logFn = function(reason) {
            console.log('Task: ' + this.subject + ' rejected for reason: ' + this.rejectedReason);    // same context anywhere
        }

        this.close(reason, logFn.bind(this) )
    }
}

task.reject('Not applicable');
// Task: New bug found rejected for reason: Not applicable

  • am extras functia in afara pentru claritate
    • am evitat sa facem bind-ul astfel: function() { ... }.bind(this)
  • acum contextul este fixat cu bind si nu mai poate fi alterat

Pe langa cele doua optiuni de fixare a contextului de executie, avem acum si posibilitatea utilizarii unei functii arrow:


let task = {
    subject: 'New bug found',
    status: 'New',
    rejectedReason: '',
    close: function(reason, logFn) {
        this.rejectedReason = reason || '';
        this.status = 'Closed';
        if (typeof logFn === 'function') 
            logFn(reason);
    },
    reject: function(reason) {

        // we use arrow function syntax
        let logFn = (reason) => {
            console.log('Task: ' + this.subject + ' rejected for reason: ' + this.rejectedReason); 
            // this is unchanged no matter the execution context  
        }

        this.close(reason, logFn)
    }
}

task.reject('Not applicable');
// Task: New bug found rejected for reason: Not applicable


Concluzie

Odata ce va obisnuiti cu noua sintaxa, probabil ca nu veti mai simti nevoia de a utiliza vechea sintaxa, fixarea contextului de executie fiind marele avantaj al functiilor arrow.

Noua sintaxa creste lizibilitatea codului atunci cand utilizam metodele de programare functionala, precum .map() sau .filter():


//ES5
var tasksList = tasks.map(function(t) { return t.subject; });
 
//ES6
var tasksList = tasks.map(t => t.name);

Ca o observatie, utilizati const atunci cand declarati functii, pentru a evita suprascrierea variabilei cu alta valoare. O functie odata definita trebuie sa ramana functie (aceeasi functie) pana la final.

comments powered by Disqus