Hvad er en ren funktion i JavaScript?

Rene funktioner er de atomare byggesten i funktionel programmering. De er elsket for deres enkelhed og testbarhed.

Dette indlæg dækker en hurtig tjekliste for at fortælle, om en funktion er ren eller ej.

Tjeklisten

En funktion skal bestå to tests for at blive betragtet som "ren":

  1. Samme indgange returnerer altid de samme udgange
  2. Ingen bivirkninger

Lad os zoome ind på hver enkelt.

1. Samme input => Samme output

Sammenlign dette:

const add = (x, y) => x + y; add(2, 4); // 6 

Til dette:

let x = 2; const add = (y) => { x += y; }; add(4); // x === 6 (the first time) 

Rene funktioner = konsekvente resultater

Det første eksempel returnerer en værdi baseret på de givne parametre, uanset hvor / hvornår du kalder det.

Hvis du passerer 2og 4, får du altid 6.

Intet andet påvirker output.

Urene funktioner = inkonsekvente resultater

Det andet eksempel returnerer intet. Den er afhængig af delt tilstand for at udføre sit job ved at øge en variabel uden for sit eget anvendelsesområde.

Dette mønster er en udviklers mareridtbrændstof.

Delt tilstand indfører en tidsafhængighed. Du får forskellige resultater afhængigt af, hvornår du ringede til funktionen. Første gang resulterer i 6, næste gang er 10osv.

Hvilken version er lettere at ræsonnere om?

Hvilken er mindre tilbøjelig til at opdrætte bugs, der kun sker under visse betingelser?

Hvilken er det mere sandsynligt, at det lykkes i et miljø med flere tråde, hvor tidsafhængigheder kan bryde systemet?

Absolut den første.

2. Ingen bivirkninger

Selve denne test er en tjekliste. Et par eksempler på bivirkninger er

  1. Muterer dit input
  2. console.log
  3. HTTP-opkald (AJAX / hent)
  4. Ændring af filsystem (fs)
  5. Forespørgsel om DOM

Dybest set ethvert arbejde, en funktion udfører, der ikke er relateret til beregning af den endelige output.

Her er en uren funktion med en bivirkning.

Ikke så slemt

const impureDouble = (x) => { console.log('doubling', x); return x * 2; }; const result = impureDouble(4); console.log({ result }); 

console.loger bivirkningen her, men i al praktisk brug vil det ikke skade os. Vi får stadig de samme output, givet de samme input.

Dette kan dog forårsage et problem.

"Urent" Ændring af et objekt

const impureAssoc = (key, value, object) => { object[key] = value; }; const person = { name: 'Bobo' }; const result = impureAssoc('shoeSize', 400, person); console.log({ person, result }); 

Variablen personer ændret for evigt, fordi vores funktion introducerede en opgaveerklæring.

Delt tilstand betyder, at impureAssocvirkningen ikke længere er helt åbenbar. At forstå dens virkning på et system indebærer nu at spore hver variabel, den nogensinde er rørt ved, og kende deres historik.

Delt tilstand = tidsafhængighed.

Vi kan rense impureAssocved blot at returnere et nyt objekt med vores ønskede egenskaber.

Rensning af det

const pureAssoc = (key, value, object) => ({ ...object, [key]: value }); const person = { name: 'Bobo' }; const result = pureAssoc('shoeSize', 400, person); console.log({ person, result }); 

pureAssocReturnerer nu et testbart resultat, og vi vil aldrig bekymre os, hvis det roligt muterede noget andetsteds.

Du kan endda gøre følgende og forblive ren:

En anden ren måde

const pureAssoc = (key, value, object) => { const newObject = { ...object }; newObject[key] = value; return newObject; }; const person = { name: 'Bobo' }; const result = pureAssoc('shoeSize', 400, person); console.log({ person, result }); 

At mutere dit input kan være farligt, men at mutere en kopi af det er ikke noget problem. Vores slutresultat er stadig en testbar, forudsigelig funktion, der fungerer uanset hvor / hvornår du kalder det.

Mutationen er begrænset til det lille omfang, og du returnerer stadig en værdi.

Deep-Cloning Objects

Heads up! Brug af spredeoperatøren ...opretter en lav kopi af et objekt. Grunde kopier er ikke sikre mod indlejrede mutationer.

Tak Rodrigo Fernández Díaz for at gøre mig opmærksom på dette!

Usikker indlejret mutation

const person = { name: 'Bobo', address: { street: 'Main Street', number: 123 } }; const shallowPersonClone = { ...person }; shallowPersonClone.address.number = 456; console.log({ person, shallowPersonClone }); 

Både personog shallowPersonCloneblev muteret, fordi deres børn deler den samme henvisning!

Sikker indlejret mutation

To safely mutate nested properties, we need a deep clone.

const person = { name: 'Bobo', address: { street: 'Main Street', number: 123 } }; const deepPersonClone = JSON.parse(JSON.stringify(person)); deepPersonClone.address.number = 456; console.log({ person, deepPersonClone }); 

Now you’re guaranteed safety because they’re truly two separate entities!

Summary

  • A function’s pure if it’s free from side-effects and returns the same output, given the same input.
  • Side-effects include: mutating input, HTTP calls, writing to disk, printing to the screen.
  • You can safely clone, thenmutate, your input. Just leave the original one untouched.
  • Spread syntax ( syntax) is the easiest way to shallowly clone objects.
  • JSON.parse(JSON.stringify(object)) is the easiest way to deeply clone objects. Thanks again Rodrigo Fernández Díaz!

My Free Course

Denne tutorial var fra mit helt gratis kursus om Educative.io, Funktionelle programmeringsmønstre med RamdaJS!

Overvej at tage / dele det, hvis du kunne lide dette indhold.

Den er fuld af lektioner, grafik, øvelser og køreeksempler, der kan køres, for at lære dig en grundlæggende funktionel programmeringsstil ved hjælp af RamdaJS.

Tak for læsningen! Indtil næste gang.