Function.prototype.call, apply és bind
2014. március 13.
by tmichel
A web alapozó tanfolyamunk JavaScript alapozó előadásán belefutottunk két olyan témába, amelyek bőven túlmutatnak egy bevezető keretein. Sőt, egy átlag weboldal fejlesztésénél sem igazán futunk bele ezekbe a dolgokba.
Ebben a bejegyzésben a Function.prototypecall
, apply
és bind
metódusairól fogok picit részletesebben írni. A minisorozat második részében pedig a new
operátor rejtélyeibe fogunk belemászni.
Metódusok
Érdemes egy pillanatra elidőzni a metódusokon. Minden nap használjuk őket, de fel sem merül bennünk, hogy valami különleges dologról lenne szó. Szerencsére nincs is, csupán az objektum-orientált programozással együtt a függvény fogalma is átalakult némileg.
Míg a függvény egy olyan képződmény, ami a külvilággal mit sem törődve végzi dolgát, addig a metódusnak (vagy más néven tagfüggvénynek) mindig van kontextusa. A metódus önmagában nem értelmezhető, mindig van egy fogadó objektuma. Ezt a fogadó objektumot sok programozási nyelven a this
kulcsszóval érhetjük el. Ez adja kontextust, ahol elérhető az objektum belső állapota.
Metódusok JavaScriptben
JavaScriptes világban a function
kulcsszó szolgál a függvények definiálására. Ez kicsit csalóka, mert a függvények lehetnek metódusok is. Sőt, ha nagyon ragaszkodom az előbbiekben leírt definícióhoz, akkor minden függvény egyben metódus is, mert JavaScriptben minden függvényben elérhető a this
kulcsszó.
Az már egy sokkal érdekesebb kérdés, hogy egy adott pillanatban a this
mire is mutat. Általánosságban elmondható, hogy arra mutat, amire számítanánk, de néha érhetnek meglepetések.
A klasszikus esetben tényleg a fogadó objektumra mutat.
var obj = { myMethod: function () { console.log(this) } } obj.myMethod() // => Object {myMethod: function}
Itt egyértelmű, hogy az obj
változóban tárolt objektumra mutat a this
, végülis a myMethod
az obj
metódusa.
A másik sokat használt eset, amikor a nagyvilágban van egy függvényem és azt hívom meg.
var f = function () { console.log(this) } f() // => Window {...}
Ebben az esetben nincs explicit fogadó objektum. Viszont a window
ott ül mindennek a tetején és a legvégén nála futnak össze a dolgok.
Ebből látszik, hogy amennyiben van explicit fogadó objektum, akkor a this
arra állítódik be, ha nincs, akkor pedig a window
-ra. Látni fogjuk, hogy akkor is ez a helyzet, ha a this
értékének null
-t vagy undefined
-ot állítunk be.
Függvényhívás kicsit másképp
Az alap koncepciót lehet kicsit bonyolítani. Gondoljunk csak a következő esetre:
var obj = { method: function (f) { f() }, otherMethod: function () { var f = function () { console.log(this) } f() } } obj.method(function () { console.log(this) }) // => Window {...} obj.otherMethod() // => Window {...} var otherObj = { f: function () { console.log(this) } } obj.method(otherObj.f) // => Window {...}
A fenti példa nagyon sarkított, és itt valójában mindegy, hogy mi a this
értéke. Viszont ha már egy callbackről beszélünk, akkor jó lenne, ha a this
értéke a helyén lenne.
Erre nyújt megoldást a call
és az apply
. Ugyanaz a feladatuk, csak a szignatúrájuk más. Mindkét metódussal a this
értékét lehet beállítani egy-egy függvényhívás esetén.
var f = function (a, b) { console.log(this, a, b) } f.call({ theAnswer: 42 }, 'hello', 'world') // => Object {theAnswer: 42} "hello" "world" f.call(null, 'hello', 'world') // => Window {...} "hello" "world" f.call(undefined, 'hello', 'world') // => Window {...} "hello" "world" f.apply({ theAswer: 42 }, ['hello', 'world']) // => Object {theAnswer: 42} "hello" "world"
A különbség csak annyi, hogy a call
-nak felsorolásszerűen kell átadni a paramétereket, míg az apply
-nak tömbként.
bind
A bind
is ugyanazt a célt szolgálja, mint az előző két metódus: előre meghatározhatjuk a this
értékét. Az előnye, hogy nem csak egyetlen függvényhívásra szól. A használata egészen egyszerű:
var boundF = f.bind('foo') boundF('hello', 'world') // => String {0: "f", 1: "o", 2: "o", length: 3} "hello" "world"
A bind szimulálása
A bind
nem alkalmaz fekete mágiát a háttérben. Nagyjából a következő kódrészlettel lehetne is szimulálni:
var bind2 = function (f, context) { return function () { f.apply(context, arguments) } }
(Az arguments
nem a semmiből terem elő: minden függvényben elérhető.)
Összegzés
A call
, apply
és bind
tehát a this
értékének a manipulálására szolgál. Érdemes ezeket észben tartani, mert egy nagyobb alkalmazás esetén már igencsak jó szolgálatot tehetnek.
A következő részben a new
operátor szépségeiben merülünk el.