Med JavaScript-funktioner kan du tilføje fælles logik til funktioner, som du ikke har kontrol over, f.eks. native og eksterne funktioner. Mange JavaScript-biblioteker, som TrackJS-agenterne, har brug for at pakke eksterne funktioner ind for at udføre deres arbejde. Ved at tilføje wrappere kan vi lytte efter telemetri, fejl og logfiler i din kode, uden at du behøver at kalde vores API eksplicit.
Du ønsker måske at pakke en funktion ind for at tilføje instrumentering eller midlertidig fejlfindingslogik. Du kan også ændre et eksternt biblioteks adfærd uden at skulle ændre kildekoden.
Basisk funktionsindpakning
Da JavaScript er vidunderligt dynamisk, kan vi indpakke en funktion ved blot at redefinere funktionen med noget nyt. Se f.eks. denne indpakning af myFunction
:
I dette trivielle eksempel har vi indpakket den oprindelige myFunction
og tilføjet en logningsmeddelelse. Men der er en masse ting, som vi ikke har håndteret:
- Hvordan sender vi funktionsargumenter igennem?
- Hvordan opretholder vi scope (værdien af
this
)? - Hvordan får vi returværdien?
- Hvad sker der, hvis der sker en fejl?
For at håndtere disse ting er vi nødt til at blive lidt mere smarte i vores indpakning.
Bemærk, at vi ikke bare påkalder funktionen i dette eksempel, men call
-er den med værdien for this
og argumenterne a
, b
og c
. Værdien for this
vil blive videregivet fra det sted, hvor du vedhæfter din indpakkede funktion, Window
i dette eksempel.
Vi har også omgivet hele funktionen i en try/catch
blok, så vi kan påberåbe os brugerdefineret logik i tilfælde af en fejl, rethrow den, eller returnere en standardværdi.
Advanceret funktionsindpakning
Det grundlæggende indpakningseksempel vil fungere 90 % af tiden, men hvis du bygger delte biblioteker, som TrackJS-agenterne, er det ikke godt nok! For at indpakke vores funktioner som en professionel er der nogle kanttilfælde, som vi skal håndtere:
- Hvad med udeklarerede eller ukendte argumenter?
- Hvordan matcher vi funktionssignaturen?
- Hvordan spejler vi funktionens egenskaber?
Der er 3 subtile, men vigtige ændringer. For det første (nr. 1) har vi navngivet funktionen. Det virker overflødigt, men brugerkode kan kontrollere værdien af function.name
, så det er vigtigt at bevare navnet, når vi wrapper.
Den anden ændring (#2) er i den måde, vi kalder den indpakkede funktion på, idet vi bruger apply
i stedet for call
. Dette giver os mulighed for at videregive et arguments
-objekt, som er et array-lignende objekt med alle de argumenter, der er videregivet til funktionen på køretid. Dette giver os mulighed for at understøtte funktioner, der kan have et udefineret eller variabelt antal argumenter.
Med apply
har vi ikke brug for de argumenter a
, b
og c
, der er defineret i funktionssignaturen. Men ved fortsat at deklarere de samme argumenter som i den oprindelige funktion, bevarer vi funktionens aritet. Det vil sige, at Function.length
returnerer det antal argumenter, der er defineret i signaturen, og dette vil afspejle den oprindelige funktion.
Den sidste ændring (#3) kopierer alle brugerspecificerede egenskaber fra den oprindelige funktion over på vores indpakning.
Begrænsninger
Denne indpakning er grundig, men der er altid begrænsninger i JavaScript. Specifikt er det svært at indpakke en funktion med en ikke-standardiseret prototype korrekt, f.eks. en objektkonstruktør. Dette er et anvendelsestilfælde, der løses bedre med arv.
Generelt er det muligt at ændre prototypen for en funktion, men det er ikke en god idé. Der er alvorlige konsekvenser for ydeevnen og utilsigtede bivirkninger ved at manipulere prototyper.
Respekt for miljøet
Function wrapping giver dig en masse magt til at instrumentere og manipulere JavaScript-miljøet. Du har et ansvar for at bruge denne magt med omtanke. Hvis du bygger funktionsindpakninger, skal du sørge for at respektere brugeren og det miljø, du opererer i. Der kan være andre wrappere på plads, andre lyttere knyttet til begivenheder og forventninger til funktions-API’er. Gå forsigtigt frem, og lad være med at ødelægge ekstern kode.