Skip to content

Instantly share code, notes, and snippets.

@Poetro Poetro/javascript-fuggvenyek.md Secret
Created Mar 11, 2011

Embed
What would you like to do?
JavaScript függvények

JavaScript függvények

A függvények a JavaScript nyelv talán legfontosabb elemei. Ugyanakkor a függvények azok, amik a legtöbb félreértés áldozatai. Ennek okán úgy gondolom érdemes tisztázni a függvények működését, és használatát. A függvények a JavaScriptben elsődleges (first-class) típusok, azaz hozzárendelhetjük őket változókhoz és objektum tulajdonságokhoz, átadhatjuk őket paraméterként más függvényeknek, valamint egy függvény illetve metódus is térhet vissza függvénnyel (egy függvényt metódusnak nevezünk, ha az egy objektum tulajdonsága). A klasszikus programozási nyelvekhez képest (C/C++, Java, PHP) ez a megközelítés idegen, ez is okozhatja a sok problémát az értelmezéssel kapcsolatban.

Függvények létrehozása

Alapvetően háromféleképpen hozhatunk létre függvényeket:

  • függvény deklarációval (function declaration)
  • függvénykifejezéssel (function expression)
  • Function konstruktor használatával.

Függvény deklaráció

A függvények létrehozásának ez a módja talán a legelterjedtebb.

function peldaFuggveny(parameter) {
  // függvény törzs
}

Ez a formája a függvényeknek szinte teljesen megegyezik a klasszikus nyelvekben való felírással, azzal a különbséggel, hogy mivel a JavaScript egy szkript nyelv, ezért nincs megadva a változók típusa, valamint hogy a függvény milyen típusú változót ad vissza, sőt az sem, hogy egyáltalán ad-e vissza valamit.

A függvény deklaráció szabályai a következők:

  1. A függvény deklarációnak kötelezően van neve,
  2. vagy Program szinten, vagy egy másik függvény törzsében szerepel,
  3. akkor jön létre, amikor a program futása abba a környezetbe jut,
  4. módosítja a környezeti változók listáját (variables object).

Ebből a listából szerintem az első pont nem szorul magyarázatra. A 2. pont azt jelenti, hogy vagy a fő programkódban (böngésző esetén közvetlenül <script> elemben), illetve egy abban levő függvény törzsében szerepelhet. Például:

<script>
function peldaFuggveny1(parameter) {
  // függvény törzs
}
</script>

illetve

function foo() {
  function peldaFuggveny2() {
    // függvény törzs
  }
}

A 3. pont pedig azt jelenti, hogy a peldaFuggveny2 nevű függvény akkor jön létre, amikor a foo függvény futása elkezdődik. Ezt a viselkedést – azaz, hogy a függvény felliftezik a környezet tetejére – nevezzük hoisting-nak. Azaz:

function foo() {
  var bar = peldaFuggveny2(); // a függvény már létezik

  function peldaFuggveny2() {
    // függvény törzs
  }
}

A 4. pont szerint pedig az elérhető változók listája kibővül a függvény nevével. Azaz egy változóhoz hozzárendelhetjük a függvényt, valamint bármilyen más helyen, ahol kifejezést használhatunk, hivatkozhatunk a függvényre a nevével.

Függvénykifejezés

A függvénykifejezés (function expression) létrehozásának szabályai a következők:

  1. a kódban ott szerepelhet, ahol kifejezés szerepelhet,
  2. neve elhagyható,
  3. nem módosítja a környezeti változók listáját,
  4. akkor jön létre, amikor a program futása rákerül.

Úgy gondolom, hogy az 1. pont szorul a legnagyobb magyarázatra. Kifejezés szerepelhet függvények, illetve konstruktorok paramétereként, és mindenhol máshol, ahol változó szerepelhet. Mit jelent az, hogy a neve elhagyható? Hogyan hivatkozunk akkor majd a függvényünkre? Hát például úgy, hogy hozzárendeljük egy változóhoz, elvégre a függvények elsődleges típusok.

// Hozzárendelhetjük változókhoz.
var foo = function () {
      // függvény törzs
    };
// Átadhatjuk paraméterként.
foo(function () {
  // függvény törzs
});

Ha a függvénykifejezésnek adhatunk nevet, de nem módosítja a környezeti változókat, akkor mire jó? Arra, hogy hivatkozni tudjunk rá a függvényünkön belül.

var foo = function bar(foobar) {
      if (foobar) {
        bar(!foobar);
      }
    };

Azonnal meghívott függvénykifejezés

A függvénykifejezés egyik speciális fajtája az azonnal meghívott függvénykifejezés (Immediately-Invoked Function Expression [IIFE]), vagy ahogy tévesen régebben nevezték self-executing anonymous function illetve self-invoked anonymous function. Ennek lényege, hogy a függvénykifejezést azonnal meghívjuk.

(function () {
  // függvény törzs
})();

Hogyan is működik ez, és miért? A zárójelek hatására a függvényünk függvénykifejezés lesz, mivel a zárójeleken belül csak kifejezés szerepelhet (amennyiben a függvény már eleve olyan helyen szerepel, ahol csak kifejezés szerepelhet, a zárójelek elhagyhatók). Ezt a létrejött függvénykifejezést pedig azonnal meg is hívjuk.

(function foo(bar) {
  if (bar) {
    foo(false);
  }
})('foobar');

Mint látható az azonnal meghívott függvénykifejezésnek nem kell anonimnak lennie, ha hivatkozni szeretnénk rá belülről, akkor adhatunk neki nevet is. Még egyszer tekintsük át, mi is történik itt valójában:

  1. Létrehozunk egy függvénykifejezést, aminek foo a neve, de kívülről nem elérhető.
  2. A függvénykifejezést meghívjuk 'foobar' paraméterrel.
  3. A függvénykifejezés meghívja magát még egyszer (most már false paraméterrel), mivel tud saját magára hivatkozni a nevével.

Miért hasznosak a függvénykifejezések?

Használatukkal elkerülhetjük a globális névtér változókkal való telepiszkítását. Ugyanis a függvényen belül var kulcsszóval létrehozott változók a függvényen kívül nem léteznek. Ugyanakkor a függvényünk azonnal lefut, és még a függvény a saját nevével se szennyezi a globális névteret, mivel a függvénykifejezés neve nem kerül a környezeti változók közé.

JScript és a függvénykifejezések

Az Microsoft JavaScript implementációja (JScript) nem specifikáció szerint működik, amikor függvénykifejezések nevéről van szó. Ugyanis ha egy függvénykifejezésnek nevet adunk, akkor azt a JScript függvény deklarációnak gondolja teljesen hibásan, azaz a környezeti változók közé is bekerül a függvény. Ezért amennyiben Internet Explorer, vagy más JScript felhasználás is a megcélzott platformok között van, akkor bánjunk óvatosan a függvénykifejezések elnevezésével (például ne ütközzön változó, vagy függvénynevekkel).

Function konstruktor

Mivel a Function konstruktor használata kerülendő, nem is foglalkozunk vele túl sokat, igazából csak az alapokat szeretném bemutatni.

var foo = new Function('bar', 'foobar', 'return bar + foobar;');

A Function konstruktor meghívásakor az átadott paraméterek közül az utolsó lesz a függvény törzse, az előtt opcionálisan megadott paraméterek pedig a függvény paraméterei. Fontos megjegyezni, hogy a new kulcsszó megadása nem kötelező, nem befolyásolja a működést.

Miért nem ajánlott használni?

Mert a megadott függvény nem abban a környezetben fut le, ahol futtatjuk, hanem a globális névtérben, ezért hozzáférhet a globális névtérben megadott változókhoz, de azokhoz nem, ahol fut.

var foo = 10;
function bar() {
  var foo = 20,
      foobar = new Function('alert(foo);');

  foobar(); // 10;
}
bar();

return utasítás

Egy függvény visszatérhet futásának bármelyik pontján a return utasítás segítségével. Amennyiben nem adtunk meg visszatérési értéket (üres return utasítás), illetve a függvény legvégén nem adtunk ki return utasítást, akkor a függvény undefined értékkel tér vissza. A függvény visszatérhet akármilyen értékkel, legyen az valamilyen primitív típus, objektum, vagy akár egy függvény is.

function foo(bar) {
  if (bar === true)
    return 10;
  else if (bar === false) {
    return function () {
      return false;
    };
  }
  // implicit return undefined
}

Ha egy függvényt a new kulcsszóval hívunk meg (azaz konstruktorként), akkor a függvény – amennyiben nem adunk ki return utasítást –, akkor az aktuális this értékkel tér vissza; és amennyiben nem adunk át paramétereket, akkor a meghívásnál a zárójelek elhagyhatók.

function Foo() {
  // implicit return this
}

new Foo;

Closure

A függvények lehetővé teszik az úgynevezett closure (lezárt) használatát. A closure lényege, hogy hozzáférünk a függvényen kívüli változókhoz azután is, hogy a függvényen kívüli környezet már lefutott. Legegyszerűbben egy példával lehet bemutatni a működést.

function foo() {
  var bar = 'foobar';
  return function barfoo() {
    alert(bar);
  }
}

var foobar = foo();
foobar(); // 'foobar'

A fenti példában létrehoztunk egy foo nevű függvényt, ami visszaad egy függvényt. A visszaadott barfoo függvény – amit a foobar nevű változóban tároltunk el –, továbbra is hozzáfér a bar nevű változóhoz, holott a foo függvény már régen lefutott. Lássuk a következő, igencsak klasszikus példát:

var foo = [], i, j;
for (i = 0; i < 5; i += 1) {
  // Függvényeket pakolunk bele a foo tömbbe.
  foo.push(function () { return i; });
}
for (j = foo.length - 1; j >= 0; j -= 1) {
  // Kiírjuk a konzolra a függvény futásának eredményét.
  console.log(foo[j]());
}

A fenti kis program 5 függvényt pakol bele a foo nevű tömbbe. A függvény visszaadja az i értékét. Ezután végigmegyünk fordított sorrendben a tömb elemein, és kiíratjuk a függvény futásának eredményét a konzolra. És egyesek meglepetésére minden esetben a konzolra az 5 íródik ki. Miért? Azért mert a függvényünk a closure-t használva hozzáfér az i értékéhez, és az első for ciklus futása után i értéke 5. Hogyan „javíthatjuk” meg a működést, azaz hogy a függvényünk azt az értéket adja vissza, mint ami i volt a létrejöttekor?

var foo = [], i, j;
for (i = 0; i < 5; i += 1) {
  // Függvényeket pakolunk bele a foo tömbbe.
  foo.push(
    // IIFE-et hozunk létre, ami bezárja egy closure-be i értékét
    function (bar) {
      // Ezt a függvényt fogjuk ténylegesen bepakolni a tömbbe
      return function () {
        // a függvény a bar változót adja vissza, ami i akkori értéke
        return bar;
      }
    }(i)
  );
}
for (j = foo.length - 1; j >= 0; j -= 1) {
  // Kiírjuk a konzolra a függvény futásának eredményét.
  console.log(foo[j]());
}

Most már a foo tömbbe pakolt függvények hozzáférnek i eredeti értékéhez (bar-hoz), mivel azt egy closure-be zártuk. További információt az MDC Closures illetve Dmitry A. Soshnikov Closures oldalain találunk.

this

A függvényeken belül a this értéke más nyelvekhez képest több dolgot jelenthet. Lássunk először is egy példát:

function foo() {
  console.log(String(this));
}
foo();
var bar = {
  foobar: foo
};
bar.foobar();

A fenti kódot böngészőben futtatva a következő kimenetet kapjuk:

[object Window]
[object Object]

Hogy is van ez? Alapvetően azok a függvények, amik nem egy objektum metódusai a window, vagy más környezetben, más globális névtér objektumot adnak vissza, this néven. Ilyen a foo függvény is. Ugyanakkor, ha mint a bar objektum metódusaként hívjuk meg, akkor azt az objektumot veszik this-nek, aminek a metódusaként hívtuk meg (példánkban ez a bar). Most fordítsuk meg a fenti példát:

var bar = {
      foobar: function () {
        console.log(String(this));
      }
    },
    foo = bar.foobar;
foo();
bar.foobar();

A fenti leírás után remélhetőleg nem lepődik meg senki, hogy ugyanazt a kimenetet kapjuk, mint először. Az ok igencsak egyszerű. A foo függvény ugyan a bar objektum egy metódusa, de nem, mint a bar objektum metódusa hívtuk meg, ezért a globális névtér objektum (a böngészőkben ez a window) lett a this.

call és apply

Minden függvény a JavaScriptben egyben objektum is, és van pár metódusa is. Ilyen metódus a call és az apply. Hogy ne legyen egyszerű az élet, a this értékét módosítani is tudjuk ezen metódusok használatával.

function foo(arg1, arg2) {
  console.log(String(this), arg1, arg2);
}
var bar = {};
foo.call(bar, "foobar", "barfoo"); // [object Object] foobar barfoo
foo.apply(bar, ["foobar", "barfoo"]); // [object Object] foobar barfoo

A függvények call metódusa első paraméterként a this-t várja, a többi paramétert pedig a függvény hagyományos paraméterként kapja meg. Az apply metódus annyiban különbözik, hogy egy tömbszerű objektumot vár, mint második paraméter, és annak elemeit adja át a függvénynek paraméterként.

Amennyiben a call, illetve apply első paramétere null, illetve undefined, akkor a this a globális névtér objektumra fog mutatni. Amennyiben valamilyen primitív érték (String, Number, Boolean) akkor az objektumként adódik át, mint this azaz lefut rajta a megfelelő típus konstruktora.

var foo = [null, undefined, NaN, true, 0, 'string', [], {}],
    i, l;
function bar(arg) {
  console.log('this: ', String(this), typeof this, ' | arg: ', arg, typeof arg);
}
for (i = 0, l = foo.length; i < l; i += 1) {
  bar.call(foo[i], foo[i]);
}

Kimenetünk:

this: [object Window] object | arg: null object
this: [object Window] object | arg: undefined undefined
this: NaN object | arg: NaN number
this: true object | arg: true boolean
this: 0 object | arg: 0 number
this: string object | arg: string string
this: object | arg: [] object
this: [object Object] object | arg: Object {} object

strict mode

A strict mode használata jelentősen módosít ezen a működésen. Jelenleg strict mode használatára nem sok lehetőség van, de azért megemlítem, miben változnak a dolgok. A call, illetve apply nem módosítja az átadott paraméter típusát, azaz a null vagy undefined értéket első paraméterként átadva null, illetve undefined lesz a this értéke. Ehhez hasonlóan a primitív típusok se alakulnak át objektummá. Ennek megfelelően, amennyiben nem adtunk meg explicit this értéket a függvény meghívásakor, illetve nem egy objektum metódusaként hívjuk meg, akkor a this nem a globális objektum lesz, hanem undefined. További információt a strict mode működéséről az MDC illetve Dmitry A. Soshnikov oldalain találunk.

arguments objektum

Egy függvény argumentumaihoz a függvényen belül az arguments objektumon keresztül is hozzáférhetünk. Ez akkor hasznos, amikor változó számú paramétert vár a függvény, illetve ki akarjuk deríteni, hogy ténylegesen mennyi paramétert is adtak át neki a meghíváskor.

function foo() {
  console.log(arguments);
}
foo('bar', 'foobar'); // ["bar", "foobar"]

Ugyan az arguments objektum hasonlít egy tömbre, de sajnos(?) nem az. Rendelekezik egy length tulajdonsággal, valamint az egyes argumentumokhoz hozzáférhetünk a megfelelő indexek használatával, például az arguments[0] visszaadja az első paramétert, példánkban ez a bar. Ennek megfelelően egyszerűen végigmehetünk az összes argumentumon:

function foo() {
  var i, l = arguments.length;
  for (i = 0; i < l; i += 1) {
    alert(arguments[i]);
  }
}

Amennyiben az argumentumokat tömbként szeretnénk kezelni, egyszerűen átalakíthatjuk, így ezek után a tömb függvényeit már tudjuk használni:

function foo() {
  var args = Array.prototype.slice.call(arguments); // az args már egy tömb.
}

Ezen kívül az arguments objektum még rendelkezik két tulajdonsággal. Az arguments.callee egy hivatkozás az éppen futó függvényre, valamint az arguments.caller egy hivatkozás a függvényt meghívó függvényre. Ez utóbbi kettő használata kerülendő, ugyanis az arguments.caller már a JavaScript 1.3 óta elavult, az arguments.callee pedig nem használható strict mode esetén.

Magasabb rendű függvények

A magasabb rendű függvények (higher order function) olyan függvények, melyek más függvényekkel dolgoznak, azaz vagy paraméterként várják őket, vagy függvényeket adnak vissza.

Függvényt fogadó függvények

A ECMAScript 5 tömb metódusainak egy része egy függvényt vár paraméternek (map, filter, reduce, reduceRight, forEach, every, some), de ilyen már régen a nyelv részét képező sort is. Ezek jellemzően egy függvényt várnak első paraméternek, és a tömb elemein futtatják le ezt a függvényt. Például a tömb sort metódusa egy opcionális függvényt vár paraméternek, ami pozitív, negatív, illetve 0 értékkel tér vissza, attól függően, hogy a két összehasonlított érték közül az elsőt nagyobbnak, kisebbnek illetve egyenlőnek tekintjük a másikkal. Például:

var array = [1, 2, 3];
// Rendezzük fordított sorrendben
array.sort(function (a, b) {
  if (a < b) {
    return 1;
  }
  else if (a > b) {
    return -1;
  }
  else {
    return 0;
  }
});
console.log(array); // [3, 2, 1]

Magunk is létrehozhatunk hasonló függvényeket:

function calculate(func, a, b) {
  return func(a, b);
}
function add(a, b) {
  returm a + b;
}
function multiply(a, b) {
  return a * b;
}
calculate(multiply, 5, 3); // 15
calculate(add, 5, 3); // 8

Amennyiben ECMAScript 5-öt nem ismerő környezetben akarunk végrehajtani a tömb összes elemén egy függvényt, akkor azt megtehetjük például a következőképpen:

function each(array, func) {
  var i, l = array.length;
  for (i = 0; i < l; i += 1) {
    func(array[i]);
  }
}
// Írjuk ki a tömb összes elemét, egyesével:
each([1, 2, 3], console.log);

Amennyiben a tömbből egy új tömböt akarunk generálni, használhatjuk például a map függvényt:

function map(array, func) {
  var i, l = array.length, retArray = [];
  for (i = 0; i < l; i += 1) {
    // Minden elemre lefut a megkapott függvény,
    // aminek eredményével feltöltjük a tömböt.
    retArray[i] = func(array[i]);
  }
  return retArray;
}
map([1, 2, 3], function (a) { return a + 1 }); // [2, 3, 4]

A nyelvben persze más függvények is tekinthetők magasabb rendűnek, ugyanis például a setTimeout és setInterval is egy függvényt vár első paraméternek. A stringek replace metódusa is opcionálisan tud függvényt fogadni második paraméterként.

Függvényt visszaadó függvények

Az is előfordulhat, hogy a függvény paramétereitől függően más függvényt szeretnénk használni, vagy generálni. Például több számhoz ugyanazt a számot szeretnénk hozzáadni, létrehozhatunk egy függvényt, ami ezt megteszi:

function addNumber(num) {
  return function (a) {
    // Closure miatt hozzáférünk a num változóhoz.
    return a + num;
  }
}
// az add10 függvény minden számhoz, amit paraméterként kap, 10-et ad hozzá.
var add10 = addNumber(10);
map([1, 2, 3], add10); // [11, 12, 13]
// Változó bevezetése nélkül, most 100-at adunk hozzá
map([1, 2, 3], addNumber(100)); // [101, 102, 103]

Magasabb rendű függvényekkel kapcsolatban érdemes elolvasni a következőket:

Összefoglalás

Mint láthattuk, a JavaScript nyelvben a függvények működése és használata igencsak összetett tud lenni. Éppen ezért minden esetben gondoljuk át, mikor, milyen formáját használjuk, valamint azt is, hogy milyen környezetben hozzuk létre. Talán kicsit sok is a buktató, viszont egy szépen felépített struktúra sokat tud hozzátenni az áttekinthetőséghez. Remélem ezzel a cikkel sikerült kicsit rendet tenni a fejekben, és többen elkezdenek elmerülni a JavaScript függvények szépségében. További olvasnivalót adhat a témában az MDC és Dmitry A. Soshnikov oldala.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.