Olen hiljattain alkanut ylläpitää jonkun toisen JavaScript-koodia. Korjaan virheitä, lisään ominaisuuksia ja yritän myös siistiä koodia ja tehdä siitä johdonmukaisempaa.
Edellinen kehittäjä käyttää kahta tapaa ilmoittaa funktioita, enkä voi selvittää, onko siihen jokin syy vai ei.
Nämä kaksi tapaa ovat:
var functionOne = function() {
// Some code
};
function functionTwo() {
// Some code
}
Mitkä ovat syyt näiden kahden eri menetelmän käyttöön ja mitkä ovat kummankin hyvät ja huonot puolet? Voidaanko toisella menetelmällä tehdä jotakin sellaista, mitä ei voida tehdä toisella?
Ero on siinä, että functionOne
on funktion lauseke ja siten määritelty vasta, kun kyseinen rivi saavutetaan, kun taas functionTwo
on funktion julistus ja määritellään heti, kun sitä ympäröivä funktio tai skripti suoritetaan (hoisting vuoksi).
Esimerkiksi funktion lauseke:
// TypeError: functionOne is not a function
functionOne();
var functionOne = function() {
console.log("Hello!");
};
Ja funktion julistus:
// Outputs: "Hello!"
functionTwo();
function functionTwo() {
console.log("Hello!");
}
Tämä tarkoittaa myös sitä, ettet voi'ehtoisesti määritellä funktioita funktiodeklaraatioiden avulla:
if (test) {
// Error or misbehavior
function functionThree() { doSomething(); }
}
Ylläoleva määrittelee functionThree
riippumatta test
'n arvosta — ellei use strict
ole voimassa, jolloin se vain aiheuttaa virheen.
Ensin haluan korjata Gregin: function abc(){}
on scoped too — nimi abc
on määritelty scope, jossa tämä määritelmä esiintyy. Esimerkki:
function xyz(){
function abc(){};
// abc is defined here...
}
// ...but not here
Toiseksi on mahdollista yhdistää molemmat tyylit:
var xyz = function abc(){};
xyz
määritellään tavalliseen tapaan, abc
on määrittelemätön kaikissa selaimissa paitsi Internet Explorer — älä luota sen määrittelyyn. Mutta se määritellään sen rungon sisällä:
var xyz = function abc(){
// xyz is visible here
// abc is visible here
}
// xyz is visible here
// abc is undefined here
Jos haluat alias-funktioita kaikissa selaimissa, käytä tällaista ilmoitusta:
function abc(){};
var xyz = abc;
Tässä tapauksessa sekä xyz
että abc
ovat saman objektin aliaksia:
console.log(xyz === abc); // prints "true"
Yksi pakottava syy käyttää yhdistettyä tyyliä on funktio-objektien "name" -attribuutti (ei tuettu Internet Explorerissa). Periaatteessa kun määrittelet funktion kuten
function abc(){};
console.log(abc.name); // prints "abc"
sen nimi annetaan automaattisesti. Mutta kun määrittelet sen kuten
var abc = function(){};
console.log(abc.name); // prints ""
sen nimi on tyhjä — loimme anonyymin funktion ja osoitimme sen jollekin muuttujalle.
Toinen hyvä syy käyttää yhdistettyä tyyliä on käyttää lyhyttä sisäistä nimeä viittaamaan itseensä ja antaa samalla pitkä, ristiriidaton nimi ulkoisille käyttäjille:
// Assume really.long.external.scoped is {}
really.long.external.scoped.name = function shortcut(n){
// Let it call itself recursively:
shortcut(n - 1);
// ...
// Let it pass itself as a callback:
someFunction(shortcut);
// ...
}
Yllä olevassa esimerkissä voimme tehdä saman ulkoisella nimellä, mutta siitä tulee liian hankalaa (ja hitaampaa).
(Toinen tapa viitata itseensä on käyttää arguments.callee
, joka on kuitenkin suhteellisen pitkä, eikä sitä tueta strict-tilassa.))
Syvällä sisimmässään JavaScript käsittelee molempia lausekkeita eri tavalla. Tämä on funktioilmoitus:
function abc(){}
abc
tässä määritellään kaikkialla nykyisessä laajuudessa:
// We can call it here
abc(); // Works
// Yet, it is defined down there.
function abc(){}
// We can call it again
abc(); // Works
Lisäksi se nostetaan return
-lauseen kautta:
// We can call it here
abc(); // Works
return;
function abc(){}
Tämä on funktion lauseke:
var xyz = function(){};
xyz
tässä määritellään osoittamispisteestä käsin:
// We can't call it here
xyz(); // UNDEFINED!!!
// Now it is defined
xyz = function(){}
// We can call it here
xyz(); // works
Funktioilmoitus vs. funktioilmaus on todellinen syy siihen, miksi Gregin osoittama ero on olemassa.
Hauska fakta:
var xyz = function abc(){};
console.log(xyz.name); // Prints "abc"
Henkilökohtaisesti pidän enemmän "function expression" -ilmoituksesta, koska näin voin hallita näkyvyyttä. Kun määrittelen funktion kuten
var abc = function(){};
tiedän, että olen määritellyt funktion paikallisesti. Kun määrittelen funktion kuten
abc = function(){};
Tiedän, että määrittelin sen globaalisti edellyttäen, etten määrittänyt abc
:tä missään kohdassa soveltamisalojen ketjussa. Tämä määrittelytyyli on kestävä myös silloin, kun sitä käytetään eval()
:n sisällä. Vaikka määritelmä
function abc(){};
riippuu asiayhteydestä ja saattaa jättää arvailun varaan, missä se oikeastaan määritellään, erityisesti eval()
— tapauksessa, vastaus on: Se riippuu selaimesta.
Tietojenkäsittelytieteen termeissä puhutaan nimettömistä funktioista ja nimetyistä funktioista. Tärkein ero on mielestäni se, että nimetön funktio ei ole sidottu nimeen, mistä nimi anonyymi funktio johtuu. JavaScriptissä se on ensimmäisen luokan objekti, joka ilmoitetaan dynaamisesti ajon aikana.
Lisätietoja anonyymeistä funktioista ja lambdalaskennasta saa Wikipediasta (http://en.wikipedia.org/wiki/Anonymous_function).