ugrás a tartalomhoz

Funkcionális JavaScript és a modul minta

Török Gábor · 2008. Jan. 13. (V), 12.15
Az alábbiakban egy kis kitérőre invitálom az olvasót. A közeljövőben a JavaScript olyan – elsősorban a funkcionális programozásból ismert – vetületeit kívánom érinteni, amelyek talán kevesebbek számára ismertek, noha akár mindennapi eszközeink közé tartozhatnának. Ez egy bejegyzés, de talán több is lesz majd belőle.

Vegyük egy függvény deklaráció vázát procedurális programozásból:
function megcsinalom(ezt)
{
    //keményen dolgozom
}
Illetve vegyük ugyanezt más szintaktikával:
var megcsinalom = function(ezt)
{
    //keményen dolgozom
}
Működésüket tekintve megegyeznek, két egymással ekvivalens felírást láttunk, hordozott jelentésükben azonban eltérnek. Utóbbi szintaktika sugallja, hogy a bevezetett függvényre ugyanazok a szabályok fognak vonatkozni, amelyet a változóknál megszoktunk; ugyanúgy hivatkozhatunk rá, ugyanúgy paraméterül adható egy másik függvénynek, ugyanaz lesz a láthatósága stb.

Igazság szerint a második nyelvtan egy névtelen függvényt hoz létre, és egyúttal egy változóhoz köti, hogy később hivatkozni lehessen rá. Bizonyára mindenki által ismert példa a req.onreadystatechange = megcsinalom; séma, amely a req [a példa kedvéért feltételezett XMLHttpRequest objektum] onreadystatchange tulajdonságának értékül adja a megcsinalom által mutatott – az esetek többségében egy függvény – objektumot. Ilyettébb módon akár azt is írhattuk volna, hogy
xmlHttp.onreadystatechange = function(allapot)
{
    //keményen dolgozom állapot valtozás esetén
}
Másik kényelmes használata a névtelen függvényeknek a lambda kalkulus. Vegyük példának okáért a storage objektum putString metódusát, amely harmadik argumentumként egy állapotkezelő callback függvényt vár. Az alábbit írhatnók fel:
storage.putString("kulcs", JSON.stringify(adatok), figyelem);
Módosítsuk úgy, hogy a referencia átadása helyett magát a (névtelen) függvény deklarációt írjuk paraméterként.
storage.putString("adatok", JSON.stringify(adatok), function(allapot)
{
    //keményen dolgozom állapot változás esetén
});
Egyik esetben sem hívjuk meg a függvényt, tehát nem a visszatérési értékét adjuk argumentumként. Megtehetnénk persze azt is.
var osszead = function(a, b) {return a + b;}
var osszeg = osszead(3, 5); //8
Helyettesítsük be a hívásba az osszead referenciája helyett magát a névtelen függvényt.
var osszeg = function(a, b) {return a + b;}(3, 5);
Érdekességképpen ugyanez Lispben.
(setf osszeg ((lambda (a b) (+ a b)) 3 5))
Ha a fenti sémát kiterjesztjük, és a függvény primitív típus helyett objektum literállal tér vissza, akkor meg is érkeztünk Douglas Crockford nyomán a JavaScript alapú fejlesztés egyik népszerű mintájához, a module patternhez.
var modul = function()
{
    //privát
    function _metodus() {}
    //publikus
    function metodus() {}
    return {metodus: metodus}
}();
A minta arra apellál, hogy egy névtelen függvényen belül létrehozott további objektumok (akár függvények) a JavaScript láthatósági szabályai értelmében csak akkor látszanak a külvilág felé, ha azokat explicit „kivisszük”. A korábbi példában bemutatott módszert alkalmazva a modul értékül fogja kapni a névtelen függvénytől visszakapott, a nyílvános tagfüggvényeket és változókat tartalmazó objektumot. A modul minta rendkívül jól alkalmazható névtér ütközések és az érzékeny adatok illetéktelen, kontrollálatlan módosításának elkerülésére.
 
1

függvény literál szintaxisa

toxin · 2008. Jan. 13. (V), 20.30
úgyemlékeztem, hogy zárójelezni kell őket, azaz

var modul = (function()  
 {  
     //privát  
     function _metodus() {}  
     //publikus  
     function metodus() {}  
     return {metodus: metodus}  
 })();
rá is kerestem a definitive guide-ban, az idevontakozó idézet

... you can define and invoke an anonymous function in a single expression. The code for this JavaScript idiom looks like this:

(function() { // This function has no name.
// Code goes here.
// Any variables declared become properties of the call
// object instead of cluttering up the global namespace.
})(); // end the function literal and invoke it now.



Note that the parentheses around the function literal are required by JavaScript syntax


JavaScript: The Definitive Guide, 5th Edition, 8.8.3. The Call Object as a Namespace

:S

üdv Csaba
2

Single expression

Török Gábor · 2008. Jan. 13. (V), 22.15
Némi különbség, hogy az idézett szöveg úgy fogalmaz,
[...] you can define and invoke an anonymous function in a single expression.

Tehát míg a
(function () { print("hello"); })();
kifejezés önállóan kiértékelhető, addig a module mintában is felhasznált
function () { print("hello"); }();
séma csak egy értékadás jobb oldalán szerepelhet, ellenkező esetben szintaktikai hibát dob az értelmező.
3

safari2

toxin · 2008. Jan. 13. (V), 23.03
köszönöm a választ, azért is érdekelt mert a

   var foo = (function () { print("hello"); })();  
kiferjezésre Safari2 alatt parse error-t kaptam

Cs.
4

lambdázás

razielanarki · 2008. Jan. 14. (H), 07.19
jó kis cikk, nagyon jól felépítve
sikerült is betömnie nálam egy fehér lyukat a js megértésében, mégpedig a lambdvábal kapcsolatban
var x = function() {} ();
mostmár tudom konkrétan mire jó a leguccsó () pár :D
thx!
5

debuggolásnál jól jön a név

tgr · 2008. Jan. 14. (H), 15.23
A gyakorlatban azért van különbség: a function foo() alakú deklarációt a debugger/profiler foo() néven fogja jelezni, a var foo = function() alakút meg function() néven, ami elég zavaró tud lenni, ha sok van belőle.
6

Hát igen

zila · 2008. Jan. 14. (H), 16.37
Jó lenne, ha debugger/profiler fejlesztők is figyelembe vennék a nyelv lehetőségeit...
7

a rekurzív hívású

toxin · 2008. Jan. 14. (H), 18.43
függvények miatt, lehet használni a

var foo = function funcFoo () { alert('foo') }
szintaxist ill. a fenti modul minta is jól működik firebug-ban, azaz funcFoo() ill. uniqIDFunc ()-t ad vissza

var uniqID = function () { var uniqID=0; return function uniqIDFunc () {return uniqID++;} }()
szvsz Cs.
8

arguments.callee

Török Gábor · 2008. Jan. 14. (H), 21.10
Rekurzív hívásokhoz praktikusabb használni a direkt erre a célra fenntartott nyelvi lehetőséget az arguments.callee személyében.

var fact = function( n )
{
    if( n == 1 ) return 1;
    return n * arguments.callee( n - 1 );
};
print( fact( 5 ) ); // 120
Vagy megspórolva a [változóhoz] kötést:

print(( function( n )
        {
            if( n == 1 ) return 1;
            return n * arguments.callee( n - 1 );
        })( 5 ));
9

csak tiszta forrásból :))

toxin · 2008. Jan. 14. (H), 22.14
azért ez is amiatt van benne, nem csak a callee

Although function literals create unnamed functions, the syntax allows a function name to be optionally specified, which is useful when writing recursive functions that call themselves. For example:

var f = function fact(x) { if (x <= 1) return 1; else return x*fact(x-1); };



This line of code defines an unnamed function and stores a reference to it in the variable f. It does not store a reference to the function into a variable named fact, but it does allow the body of the function to refer to itself using that name. Note, however, that this type of named function literal was not properly implemented before JavaScript 1.5.



from Javascript Definitive Guide 8.1.2. Function Literals , jó a callee-ra tényleg azt írják hogy

8.2.2.1. The callee Property
In addition to its array elements, the Arguments object defines a callee property that refers to the function that is currently being executed. This property is rarely useful, but it can be used to allow unnamed functions to invoke themselves recursively. For instance, here is an unnamed function literal that computes factorials:

function(x) {
if (x <= 1) return 1;
return x * arguments.callee(x-1);
}




Cs.
10

hozzászólás

inf3rno · 2008. Már. 2. (V), 06.03
Szia!

Én nem vágom mire jó ezzel trükközni :-) ismerem ezt a mintát, de sosem tartottam a gyakorlatban használhatónak. Mmint a modul mintát tutira nem használnám sehol a többit, amit írtál viszont nagyon gyakran.
Én úgy vagyok vele, hogy elfogadtam, hogy jsben nincs olyan szinten private változó, mint a legtöbb nyelvben, szóval nem szenvedtem vele sokat.. max extrém esetekben jó a dolog, de ahol már öröklés meg komolyabb dolgok vannak, ott nem ér semmit. (nekem legalábbis ebbe tört bele a bicskám, amikor private változós classeket akartam még jó régen csinálni jsben)

Persze ettől függetlenül, ha tudsz komoly példát mutatni, ahol tényleg nagyon hasznos a dolog, akkor azt mondom oks :) tévedtem



szerk:
közben leesett, hogy használok elég sok helyen hasonló dolgokat, csak hát így jár, aki nem ismeri a minták elnevezéseit :P
pl:

Function.prototype.Mask=function (environment)
{
	if (!environment)
	{
		environment={}
	}
	environment.callee=this;
	if (!environment.mask)
	{
		environment.mask=function ()
		{
			return this.callee.apply((this.destination || this.source),arguments);
		}
	}
	var method=this;
	return function ()
	{
		environment.source=this;
		return environment.mask.apply(environment,arguments);
	}
}
példa egy használatra:


Array.prototype.push=Array.prototype.push.Mask(
{
	mask: function (value)
	{
		this.callee.call(this.source,value);
		return (this.destination || this.source);
	}
});

mondjuk nem tudom ez mennyire fedi le a témát, de azt hiszem elég erős technológia :-) utóbbi napokban állt össze a fejemben, hogy nekem kell egy ilyen

(aki nem értené a példában a tömb push metódusát írtam felül úgy, hogy az eredeti pusht hívja meg, de szigorúan csak egy hozzáfűzendő elemmel, a visszatérő érték pedig maga a tömb lesz, és nem a tömb hossza)