Med hjälp av JavaScript-funktioner kan du lägga till gemensam logik i funktioner som du inte har kontroll över, t.ex. inbyggda och externa funktioner. Många JavaScript-bibliotek, som TrackJS-agenterna, behöver omsluta externa funktioner för att utföra sitt arbete. Genom att lägga till wrappers kan vi lyssna på telemetri, fel och loggar i din kod, utan att du behöver anropa vårt API explicit.
Du kanske vill wrapa en funktion för att lägga till instrumentering eller tillfällig felsökningslogik. Du kan också ändra beteendet hos ett externt bibliotek utan att behöva ändra källkoden.
Basisk funktionsinpackning
Då JavaScript är fantastiskt dynamiskt kan vi packa in en funktion genom att helt enkelt omdefiniera funktionen med något nytt. Tänk till exempel på den här omslaget av myFunction
:
I det här triviala exemplet har vi omslagit det ursprungliga myFunction
och lagt till ett loggningsmeddelande. Men det finns en hel del saker som vi inte har hanterat:
- Hur skickar vi igenom funktionsargument?
- Hur upprätthåller vi räckvidden (värdet av
this
)? - Hur får vi fram returvärdet?
- Hur gör vi om ett fel inträffar?
För att hantera dessa saker måste vi bli lite smartare i vår omslagsform.
Bemärk att vi inte bara anropar funktionen i det här exemplet, utan call
-ar den med värdet för this
och argumenten a
, b
och c
. Värdet för this
kommer att skickas vidare från den plats där du bifogar din inplastade funktion, Window
i det här exemplet.
Vi har också omgett hela funktionen i ett try/catch
-block så att vi kan åberopa egen logik i händelse av ett fel, återkalla det eller returnera ett standardvärde.
Advanced Function Wrapping
Det grundläggande omslagsexemplet fungerar i 90 % av fallen, men om du bygger delade bibliotek, som TrackJS-agenterna, är det inte tillräckligt bra! Om du vill slå in våra funktioner som ett proffs finns det några kantfall som vi bör hantera:
- Hur är det med odeklarerade eller okända argument?
- Hur matchar vi funktionssignaturen?
- Hur speglar vi funktionens egenskaper?
Det finns 3 subtila men viktiga förändringar. För det första (nr 1) namngav vi funktionen. Det verkar överflödigt, men användarkod kan kontrollera värdet på function.name
, så det är viktigt att behålla namnet vid omslaget.
Den andra ändringen (#2) gäller hur vi kallar den omslagna funktionen, genom att använda apply
i stället för call
. Detta gör det möjligt för oss att skicka igenom ett arguments
-objekt, som är ett array-liknande objekt av alla argument som skickas till funktionen vid körning. Detta gör att vi kan stödja funktioner som kan ha odefinierat eller variabelt antal argument.
Med apply
behöver vi inte argumenten a
, b
och c
som definieras i funktionssignaturen. Men genom att fortsätta att deklarera samma argument som den ursprungliga funktionen behåller vi funktionens aritet. Det vill säga, Function.length
returnerar det antal argument som definieras i signaturen, och detta kommer att spegla den ursprungliga funktionen.
Den sista ändringen (#3) kopierar alla användarspecificerade egenskaper från den ursprungliga funktionen till vår omslagsform.
Begränsningar
Denna omslagsform är grundlig, men det finns alltid begränsningar i JavaScript. Specifikt är det svårt att korrekt omsluta en funktion med en icke-standardiserad prototyp, t.ex. en objektkonstruktör. Detta är ett användningsfall som löses bättre med arv.
I allmänhet är det möjligt att ändra prototypen för en funktion, men det är ingen bra idé. Det finns allvarliga prestandaimplikationer och oavsiktliga bieffekter när man manipulerar prototyper.
Respektera miljön
Function wrapping ger dig stora möjligheter att instrumentera och manipulera JavaScript-miljön. Du har ett ansvar att använda den makten på ett klokt sätt. Om du bygger funktionsförpackningar ska du se till att respektera användaren och den miljö du verkar i. Det kan finnas andra wrappers på plats, andra lyssnare kopplade till händelser och förväntningar på funktionernas API:er. Gå försiktigt fram och bryt inte sönder extern kod.