var functionName = function() {} vs function functionName() {}

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?

Ratkaisu

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.

Kommentit (27)

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.

Kommentit (18)

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).

Kommentit (0)