(function() {})();
JavaScripts kanskje mest kryptiske konstruksjon er et hendig hjelpemiddel for å beskytte det globale skopet. Vi ser på hva dette er og hvordan vi kan bruke det.
Hva er dette?
I sin vanligste form ser den ut som for eksempel:
(function() {
// Kode her
})();
Innerst finner vi: function() { /* Kode */ } - altså en helt vanlig funksjon. Som vi har sett tidligere er dette en funksjon uten navn - en anonym funksjon. En anonym funksjon kan ikke refereres senere med mindre den tilegnes et objekt, en variabel, eller sendes som parameter til en annen funksjon.
Konstruksjonen ovenfor er altså intet mindre enn en anonym funksjon som umiddelbart kalles, med andre ord: function() { /* Kode */ }();. Nå mangler vi bare to paranteser. Kjør den forrige kodesnutten og du oppdager fort hvor de kommer fra: det er ikke lov å plassere en parantes rett etter den avsluttende klammeparantesen, det er syntaksfeil. Derfor stapper vi funksjonen i to paranteser, og kaller dermed funksjonen: (<funksjon>)();
Og hva skal vi med dette?
Som jeg nevnte tidligere kan ikke denne funksjonen refereres senere fordi den er anonym og ikke tilordnes noe sted. Den bare kjøres. Dette byr på et par nyttige bruksområder:
- Lokal kode inne i funksjonen "lekker" ikke ut i det globale navnerommet, ei heller den anonyme funksjonen selv
- JavaScript mangler blokkskop, men anonyme funksjoner som dette kan når som helst brukes til å innføre skop
Beskyttelse fra det globale skopet
Når vi koder JavaScript utenfor objekter og funksjoner koder vi i det implisitt globale skopet. Dette anses som dårlig praksis fordi det oppfordrer lite til god organisering, og fordi sjansene for kollisjon med andres kode er svært stor. Tror du for eksempel at du er alene om å lage et objekt som heter "Lightbox"? Eller en "lokal" variabel ved navn "element"? Tro igjen.
En måte å unngå bieffekten at all kode som ikke er skopet blir sittende i det globale skopet er å flytte all koden inn i en anonym funksjon. Dersom koden tilbyr et slags API som andre skal bruke er du nødt til å eksponere noe til det globale skopet, og dette kan da gjøres på en tre måter.
Definer globale navnerom først
De objektene som skal være tilgjengelig for andre enn den indre delen av koden deklareres først. Public funksjonalitet kan så tilordnes disse fra inne i den anonyme funksjonen:
var MyAPI = {};
(function() {
// Kode
MyAPI.publicObj = {};
})();
Returner public API
En annen variant av eksempelet over er å la den anonyme funksjonen returnere det som skal være public. Da trenger du en anonym funksjon per åpne API:
var MyAPI = (function() {
var publicObj = {};
// Kod funksjonalitet i public
return publicObj;
})();
Skriv til det eksplisitte globale skopet
En siste mulighet er å skrive til window-objektet når du trenger å eksponere funksjonalitet utenfor den anonyme funksjonen. Denne metoden er langt mindre åpenbar for å andre - den krever at andre leser koden din for å finne all funksjonalitet som eksponeres, og bør med hell unngås:
(function() {
window.MyAPI = {};
// Kod funksjonalitet rett i window.MyAPI
})();
Innføre skop
En annen fordel med de anonyme funksjonene er at du kan innføre skop der du trenger det - noen ganger er det helt nødvendig. Et eksempel der dette behovet oppstår er når vi looper et objekt og bruker enkeltverdiene i closures, feks i eventhandlere. Se på denne koden (her er vi interessert i mønsteret, ikke detaljene):
var links = document.getElementsByTagName("a");
for (var i = 0, link; i < links.length; i++) {
link = links[i];
link.onmouseover = function(e) {
console.log(link.innerHTML);
}
}
Denne koden ser tilforlatelig ut, men gir deg neppe resultatene du forventer. Mouseover på samtlige lenker på siden vil her logge lenketeksten til den siste lenken på siden - ikke "seg selv". Hvorfor det?
Eventhandlerene danner hver sin closure. Alle handlerene har en referanse til sin kontekst - den samme konteksten. I denne konteksten finnes det kun én link, og denne oppdateres jo hver gang løkka går. Den siste verdien variabelen får blir stående, og deles mellom alle handlerne.
I slike tilfeller er løsningen å opprette enda en closure for å skope den lokale variabelen link til blokka. Dette kan vi gjøre på flere måter, her er to:
var links = document.getElementsByTagName("a");
for (var i = 0; i < links.length; i++) {
(function() {
// Definerer en lokal variabelen i den anonyme closuren som
// opprettes på nytt for hver runde i loopen
var link = links[i];
link.onmouseover = function(e) {
console.log(link.innerHTML);
}
})();
}
var links = document.getElementsByTagName("a");
for (var i = 0, link; i < links.length; i++) {
link = links[i];
link.onmouseover = (function() {
// Samme prinsipp som over, bare annen plassering av closuren
var linkElement = link;
// Må returnere funksjonen som skal være eventhandler
return function(e) {
console.log(linkElement.innerHTML);
}
})();
}
Vi kan også sende verdiene vi trenger å fryse som parameter til closuren:
var links = document.getElementsByTagName("a");
for (var i = 0, link; i < links.length; i++) {
link = links[i];
link.onmouseover = (function(linkElement) {
return function(e) {
console.log(linkElement.innerHTML);
}
})(link);
}
Denne løsningen gir selv i små trivielle eksempler løsninger som ikke er så intuitive som de kanskje burde ha vært. Men det funker, og noen ganger kan dette være den beste løsningen.
Senere i julekalenderen ser vi på et litt større kodeeksempel der blant annet bruken av (function() {}) blir eksemplifisert på flere måter.
Vel møtt i morgen for en prat om prototyper og hvordan disse kan brukes til å modifisere blant annet JavaScripts innebygde typer.
Kommentarer
skop
(http://skop.com)
20. desember, 17:52
Christian
20. desember, 20:23
Å skrive på norsk om kode er generelt sett en utfordring...