NF1 - Estructures definides pel Programador - Funcions
Contingut
- 1 Funcions
- 1.1 Les Funcions son fonamentals
- 1.2 Les funcions sense nom
- 1.3 Sobrecàrrega de funcions
- 1.4 Clausures
- 1.5 Funcions anònimes, autoexecutables i que retornen funcions
- 1.6 Funcions immediates
- 1.7 Les funcions aninades
- 1.8 Exercicis
- 2 Bibliografia
Funcions
Les Funcions son fonamentals
Les funcions, en Javascript, són objectes de primera classe, és a dir, coexisteixen amb qualsevol altre objecte i poden tractar-se com un d'ells. Igual que els tipus més mundans de Javascript, les variables poden fer referència a elles, es poden declarar amb literals i fins i tot passar-se com a paràmetres d'altres funcions.
La funció és la principal unitat modular d'execució. Vol dir que excepte els comandos incrustats en el codi que s'executen mentre es avalua les etiquetes, tot la resta de les nostres pàgines està dins d'una funció.
Objectes
La forma més sencilla de crear un nou objecte és amb una intrucció com aquesta :
var obj1 = new Object();
var obj2 = {};
Això crea un objecte nou, buit i que més tard podem ampliar amb propietats:
var o = {};
o.name = "Marta";
o.feina = "contable";
o.antiguitat = 11;
Per eliminar una propietat podem utilitzar l'operador delete:
var obj1 = new Object()
alert(obj1.nombre) // undefined
obj1.nombre = "mundo"
alert(obj1.nombre) // "mundo"
delete obj1.nombre
alert(obj1.nombre) // undefined otra vez
Per afegir un mètode podem fer:
var obj1 = new Object();
obj1.nombre = "mundo";
obj1.saluda = function() { alert("hola "+this.nombre) };
obj1.saluda();
També podem construir objectes de manera literal:
var obj1 = {nombre:"mundo",
saluda: function() { alert("hola "+this.nombre) }
};
obj1.saluda();
I podem accedir als mètodes i a les propietats de diferents maneres:
var obj1 = new Object();
obj1.nombre = "mundo";
obj1.saluda = function() { alert("hola "+this.nombre) };
alert("hola " + obj1.nombre); // Acceso normal
alert("hola " + obj1[ "nombre" ]); // Acceso indexado con un literal
var prop = "nombre";
alert("hola " + obj1[ prop ]); // Acceso indexado con una variable
obj1.saluda(); // Invocación normal
obj1[ "saluda" ](); // Acceso indexado con un literal
var func = "saluda";
obj1[ func ](); // Acceso indexado con una variable
Mètodes privats Aquesta part ja s'ha vist en el capitol de les funcions, però no està de més tornar a fer una ullada:
var Saludator = function(nom) {
this.nombre = nom
this.saluda = function() {
alert("hola "+plural(this.nombre))
}
function plural(n) {
return n + "s"
}
}
var obj1 = new Saludator("mundo")
obj1.saluda()
// obj1.plural() // fallaría porque plural() es privado
Els que venim d'un entorn orientat a objectes clàssic trobaran a faltar la encapsulació i la estructuració que implica el concepte de constructor de classe. Javascript proporciona aquest mecanisme però d'una manera molt diferent. En Javascript totes les funcions i els objectes tenen una propietat anomenada prototype que fa referència a un objecte buit de forma inicial. Aquest objecte buit es crea utilitzant la paraula new.
Les funcions com a objectes de primera classe
Els objectes tenen les següents capacitats:
- Es creen a través de literals.
- S'assignen a variables, entrades de matriu i propietats d'altres objectes.
- Es poden passar com a arguments per a funcions.
- Es retornen com a valors a partir de funcions.
- Tenen propietats que poden crear-se i assignar-se de forma dinàmica.
Les funcions tenen totes aquestes capacitats, a més tenen la capacitat que poden invocar-se.
Exemple :
var parOimpar = (function() {
var hoy = new Date()
if (new Date().getDate() % 2 == 0) {
return function() { alert("hoy es dia par") }
} else {
return function() { alert("hoy es dia impar") }
}
})()
parOimpar();
Declaracions
Les funcions es declaren usant una funció literal que crea un valor de la mateixa manera que un literal numèric. Les funcions són valors que poden emprar-se en el llenguatge igual que altres valors, com les cadenes o els nombres. Les funcions literals es componen de quatre parts:
- 1. La paraula clau function
- 2. Un nom opcional que, si s'especifica, ha de ser un identificador vàlid.
- 3. Una llista separada per comes de noms de paràmetres entre parèntesis. La llista pot estar buida, però els parèntesis han d'estar presents.
- 4. El cos de la funció. Una sèrie d'instruccions entre claus.
El fet que el nom de la funció sigui opcional pot ser una sorpresa. Si no tenim necessitat de fer referència a elles pel seu nom, no hem de donar-li-ho. Quan se li posa un nom a una funció, és vàlid en tot l'àmbit en el qual aquesta es declara. Si una funció es declara amb nom el nivell superior, es crea una propietat usant el nom de la funció en l'objecte window. Finalment, totes les funcions tenen una propietat anomenada name que emmagatzema el nom de la funció com una cadena. Estarà buida en el cas que no li posem nom. Sintaxi d'una funció Javascript:
function nomFuncio()
{
var x=5;
return x;
}
Anem a provar tot el que hem dit anteriorment amb un exemple. Utilitzarem un joc de proves per veure si realment les funcions són objectes de primera clase:
// --- fitxer on posem les declaracions de funcions ---
function lleugera() {
return true;
}
var sensenom = function(){return true;};
window.esVeritat = function(){return true;};
function externa(){
function interna(){}
}
var una_funcio = function una_altre_funcio(){return true;};
Joc de proves:
test("Conjunt de Tests per a funcions", function() {
equal(typeof window.lleugera === "function", true);
equal(lleugera.name === "lleugera", true);
equal(lleugera.name === "lleugera", true);
equal(typeof window.sensenom === "function", true);
equal(sensenom.name === "", true);
equal(typeof window.esVeritat === "function", true);
equal(typeof window.externa === "function", true);
equal(typeof window.interna === "function", false);
equal(window.una_funcio.name === "una_altre_funcio", true);
});
Que demostra el joc de proves?
- Les funcions s'afegeixen com a propietats del objecte window.
- Les funcions tenen una propietat anomenada name
- Que window.sensenom es defineix com una funció demostrant que les variables globals, fins i tot les que contenen funcions, terminen en window.
- Que window.esVeritat es defineix com una funció.
Exercici 4
Crea el teu propi framework tests unitaris. Ara que ja sabem definir funcions crea el teu propi framework. El nucli d'un entorn de comprovació única es el seu mètode conegut com a assert(). Aquest rep dos paràmetres, un valor i una descripció. Si el valor s'avalua com a cert, la assert s'avalua com a correcte. Si es fals, llavors l'assert s'avalua com a fallada.
Àmbit de les variables
L'àmbit d'una variable (anomenat "scope" en anglès) és la zona del programa en la qual es defineix la variable. JavaScript defineix dos àmbits per a les variables: global i local.
El següent exemple il·lustra el comportament dels àmbits:
function creaMensaje () {
var missatge = "Missatge de prova";
}
creaMensaje ();
alert (missatge);
L'exemple anterior defineix en primer lloc una funció anomenada creaMensaje que cregui una variable anomenada missatge. A continuació, s'executa la funció mitjançant l'anomenada creaMensaje (); i tot seguit, es mostra mitjançant la funció alert () el valor de una variable anomenada missatge.
No obstant això, en executar el codi anterior no mostra cap missatge per pantalla. La raó és que la variable missatge s'ha definit dins de la funció creaMensaje () i per tant, és una variable local que només està definida dins de la funció.
Qualsevol instrucció que es trobi dins de la funció pot fer ús d'aquesta variable, però totes les instruccions que es trobin en altres funcions o fora de qualsevol funció no tindran definida la variable missatge. D'aquesta manera, per mostrar el missatge en el codi anterior, la funció alert () ha de cridar des de dins de la funció creaMensaje ():
function creaMensaje () {
var missatge = "Missatge de prova";
alert (missatge);
}
Què passa si una funció defineix una variable local amb el mateix nom que una variable global que ja existeix? En aquest cas, les variables locals prevalen sobre les globals, però només dins de la funció:
var missatge = "guanya la de fora";
function muestraMensaje () {
var missatge = "guanya la de dins";
alert (missatge);
}
alert (missatge);
muestraMensaje ();
alert (missatge);
El codi anterior mostra per pantalla els següents missatges:
guanya la de fora
guanya la de dins
guanya la de fora
Dins de la funció, la variable local anomenada missatge té més prioritat que la variable global del mateix nom, però només dins de la funció. Què passa si dins d'una funció es defineix una variable global amb el mateix nom que una altra variable global que ja existeix? En aquest altre cas, la variable global definida dins de la funció simplement modifica el valor de la variable global definida anteriorment:
var missatge = "guanya la de fora";
function muestraMensaje () {
missatge = "guanya la de dins";
alert (missatge);
}
alert (missatge);
muestraMensaje ();
alert (missatge);
En aquest cas, els missatges mostrats són:
guanya la de fora
guanya la de dins
guanya la de dins
La recomanació general és definir com a variables locals totes les variables que siguin d'ús exclusiu per a realitzar les tasques encomanades a cada funció. les variables globals s'utilitzen per compartir variables entre funcions de forma senzilla.
Exercici 5
Observa el següent codi i respon:
function externa(){
var a = 1;
function interna(){ /* no fa res */ }
var b = 2;
if(a ==1 ) {
var c = 3;
}
}
externa();
Quin és l'àmbit de (digues on comença i on acaba):
- externa
- a
- interna
- b
- c
Exercici 6
Utilitza el teu framework per comprovar l'exercici anterior. Comprova els àmbits de totes les variables i funcions en llocs estratègics del codi
Invocacions
Hi ha quatre maneres diferents per invocar a una funció, cadascuna amb les seves peculiaritats.
- 1. Com una funció, en la qual aquesta s'invoca de manera senzilla.
- 2. Com un mètode, que vincula la invocació a un objecte, habilitant la programació orientada a objectes.
- 3. Com un constructor, en el qual un nou objecte es fa realitat.
- 4. A través dels seus mètodes apply() o bé call()
Dels arguments als paràmetres de les funcions
Si el nombre d'arguments i paràmetres és diferent, no hi ha error:
- Si hi ha mes arguments que paràmetres, els arguments de sobres no s'assignen als noms de paràmetres.
function qualsevol(a,b,c){}
qualsevol(1,2,3,4,5,6); //4,5,6 no s'assignen a cap paràmetre.
- Si hi ha mes paràmetres que arguments, els paràmetres que no tinguin el seu argument corresponent s'estableixen com undefined
function qualsevol(a,b,c){}
qualsevol(1); //b,c tenen el valor undefined
En totes les invocacions de les funcions es passen 2 paràmetres implícits (es passen en silenci i estan en el seu àmbit) : arguments i this.
- Arguments
Arguments és una col·lecció de tots els arguments que s'han passat a la funció. Té una propietat length que conté el numero de paràmetres que s'han passat. Els valors dels paràmetres es pot obtenir com en un array. Exemple: arguments[2] ens dóna el tercer paràmetre.
- This
És el context de la funció. En JAVA this és la instància de la classe en la qual es defineix el mètode. En Javascript no et confiïs. El paràmetre this no es defineix, com en Java, per com es declara la funció, sinó per com s'invoca.
Invocació com una funció
És la manera normal d'invocar a una funció en qualsevol llenguatge. Exemple:
function cridam(){};
cridam();
var unaltre = function(){};
unaltre();
Quan ho fem d'aquesta manera el contexte de la funció és el global. Aixó vol dir que la funció és una propietat del objecte window. Però donem per implícit aquest objecte en la seva crida. Realment, aquesta manera és la mateixa que la invocació com a mètode, ja que aquestes funcions són mètodes de l'objecte window.
Invocació com un mètode
Es produeix quan s'assigna una funció a la propietat d'un objecte. Exemple:
//es crea l'objecte anomenat 'o'
var o = {};
o.nom_metode = funtion(){};
//es defineix una propietat anomenada 'nom_metode' i se li assigna una funció.
//aquesta propietat la hem convertit en un mètode.
o.nom_metode(); //crida al mètode.
Quan invoquem d'aquesta manera a la funció, l'objecte es converteix en el contexte de la funció, es a dir, el paràmetre implícit this correspon al objecte.
Què és exactament this
La paraula clau this té en Javascript un comportament diferent al d'altres llenguatges però en general, el seu valor fa referència al propietari de la funció que l'està invocant o, si no, a fi on aquesta funció és un mètode.
La paraula clau del paràgraf anterior és "propietari".
Quan no estem dins d'una estructura definida, això és un objecte amb mètodes, el propietari d'una funció és sempre el context global. En el cas dels navegadors web, hem de recordar que aquest objecte és window:
console.log( this === window ); // true
function test(){
console.log( this === window);
}
test(); // true
Accedint als valors d'un objecte des del propi objecte
Aquest concepte de propietari pot induir a errors. Pensem en un objecte amb una sèrie de propietats:
var myApp = {
name : 'Megan',
lastName : 'Fox',
birthDate : '16/05/1986',
isPretty : true
};
console.log( myApp.name ); // Megan
console.log( myApp.isPretty ); // true
Suposem ara que necessitem una altra propietat més 'dinàmica' que participi dels valors assignats a qualsevol altra. Per exemple, volem un 'completeName "que concatene' name 'i' lastname '. Sembla que aquest un clar exemple d'ús per a this:
var myApp = {
name : 'Megan',
lastName : 'Fox',
completeName : this.name + this.lastName
}
Encara que sembla coherent, quan passem a comprovar-ho veiem que el resultat no és l'esperat:
console.log (myApp.completeName); // undefined
El problema aquí és que dins d'aquest no està apuntant a l'objecte com es podria esperar, sinó que està buscant la referència fora, en el context global (finestra).
Per obtenir el resultat esperat hem d'aplicar un patró d'invocació que modifiqui al propietari des del qual s'invoca el this ...
Patró d'invocació per mètode
En el desenvolupament d'aplicacions modernes, el patró més recurrent és el d'invocació per mètode: una funció és emmagatzemada com a propietat d'un objecte convertint-se així en el que anomenem un mètode.
Quan diem (invoquem) a un mètode, this fa refencia al mateix objecte:
var myApp = {
name : 'Megan',
lastName : 'Fox',
completeName : function(){
return this.name + ' ' + this.lastName;
}
}
console.log( myApp.completeName() ); // Megan Fox
En aquesta ocasió, si podem comprovar com this apunta al mateix objecte i busca la propietats 'name' i 'lastname' dins en lloc de remuntar-se fins el context global.
Exercici 7
¿Quin és el seu valor?
var myApp = function(){
var name = "World"
var sayHello = function(){
console.log( 'Hello, ' + this.name );
};
sayHello();
};
myApp();
Aquest comportament el podem comprovar si creem una variable global amb aquell nom pel qual estem preguntant
¿Quin és el seu valor?
var name = "Strange World";
var myApp = function(){
var name = "World"
var sayHello = function(){
console.log( 'Hello, ' + this.name );
};
sayHello();
};
myApp();
La conseqüència d'aquest error és que un mètode no pot utilitzar funcions internes que l'ajudin a fer la seva feina perquè aquestes, no té accés a les seves propietats.
Exercici 7.1
Mostra amb un framework unitari qui és l'objecte del argument this en cada cas:
function unica(){return this;};
var replica = unica;
var objecte1 = {
clon : unica
}
var objecte2 = {
clon : unica
}
S'ha d'observar que el contexte canvia depenent de com s'invoca la funció. Pensa que la funció és la mateixa en tots els casos. Objecte1 i Objecte2 comparteixen la mateixa instància de la funció, fins i tot quan s'executa, la funció té accés al objecte que va invocar el mètode i pot realitzar operacions amb ell. Aquest és el principi de la programació orientada a objectes.
Exercici 8
var persona = {
name: 'edu',
twitter: 'eiximenis',
twitterUrl: 'http://twitter.com/' + this.twitter
};
console.log(persona.twitterUrl);
Exercici 8.1
console.debug(this);
function test(){
console.debug(this);
}
test();
Exercici 8.2
var obj = {
name: 'obj',
run: function(){
this.value = 1;
console.debug(this.name);
}
};
obj.run();
Exercici 8.3
var obj = {
name: 'obj',
run: function(){
this.value = 1;
console.debug(this);
(function(){ // se crea un nuevo scope
console.debug(this);
})();
function test(){ // se crea un nuevo scope
console.debug(this);
}
test();
}
};
obj.run();
Exercici 8.4
var obj = {
name: 'obj',
run: function(){
this.value = 1;
console.debug(this);
(function(){
console.debug(this);
}).call(this); // se autoejecuta con el método call
this.test = function(){
console.debug(this);
}
this.test();
}
};
obj.run();
Invocació com constructor
Per invocar una funció com a constructor no té cap misteri. S'ha de posar la paraula new davant de la funció. El que té de interessant és el que passa quan ho fem:
- Es crea un objecte buit.
- Aquest objecte es passa al constructor com paràmetre this i d'aquesta manera es converteix en el contexte de la funció.
- En absència de qualsevol valor retornat de forma explícita, el nou objecte es retorna com a valor del constructor.
Que passaria si invoquem a la funció sense utilitzar la paraula new?
- doncs que no es crea l'objecte i, llavors, les propietats i mètodes que pugui haver pertanyen al objecte window.
Com sabem si una funció s'utilitza per crear objectes o son funcions normals?
- Doncs no hi ha una manera estàndard
- Per convenció si les funcions creen objectes llavors el nom de la funció comença amb lletra MAJÚSCULA.
function Cotxe(){
this.matricula = "";
}
/*cridem a la funció d'aquesta manera:*/
var cotxe1 = new Cotxe();
En aquest exemple, la propietat matricula pertany a l'objecte cotxe1. Si no haguéssim posat la paraula new el paràmetre this que s'utilitza dintre de la funció faria referència al objecte window. Com s'ha posat la paraula new el paràmetre this fa referència al nou objecte creat.
Invocació amb els mètodes apply() i call()
Javascript ens proporciona un mitjà per invocar a una funció i especificar de forma explícita qualsevol objecte que vulguem com a context. És a dir, podem decidir quin és el valor del paràmetre this quan cridem a una funció. S'aconsegueix amb qualsevol dels dos mètodes que posseeixen les funcions: apply() i call(). Sí, totes les funciones tenen dos mètodes disponibles, ja que son objectes de primera classe poden tindre propietats i mètodes igual que qualsevol altre objecte. Paràmetres del mètode apply():
- L'objecte que s'utilitzarà com a context de la funció
- Array de valors que s'utilitzaran com a arguments de la funció.
Paràmetres del mètode call():
- L'objecte que s'utilitzarà com a context de la funció
- Llistat de valors que s'utilitzaran com a arguments de la funció.
Exemple:
function exemple(){
var result = 0;
for (var i = 0; i < arguments.length; i++){
result += arguments[i];
}
this.result = result;
}
var objecte1 = {};
var objecte2 = {};
exemple.apply(objecte1,[1,2,3,4]);
exemple.call(objecte2,5,6,7,8);
// en aquest punt objecte1.result tindrà el valor 10
// i objecte2.result tindrà el valor 26
Exercici 9
Digues que es mostrarà a cada alert. Explica-ho.
var missatge2 = "hola"
function Nova (){
var missatge2 = "adeu";
this.missatge2 = "hello";
alert("Missatge2 local: " + missatge2 + " ; Missatge2 global: " + this.missatge2);
}
var e = new Nova();
alert(e.missatge2);
alert("window : " + missatge2);
Les funcions sense nom
Les funcions sense nom s'utilitzen quan volem utilitzar-les posteriorment. Exemples:
- quan les emmagatzemem en una variable,
- quan les posem com a mètodes d'objectes
- quan les utilitzem com devolució de trucada (callback de timeout, callback de controladors d'events, etc).
window.onload = function () {assert(true, "2");};
var obj = {
print : function () {assert(true, "1");}
}
obj.print();
setTimeout(function(){ assert(true, "3"); }, 500);
L'ordre de sortida per pantalla és el següent : 1,2,3 També podíem haber fet el següent amb el controlador de l'event càrrega de pàgina:
function bootMeUp(){
assert(true, "2");
};
window.onload = bootMeUp;
Però, per qué donar-li nom si mai més la cridarem? Realment necessitem que bootMeUp sigui un mètode del objecte window? A més a més, podem pensar que el mètode print és el nom de la funció anónima que l'assignem. NO!!. Demostra-ho utilitzant la propietat name de les funcions.
Funcions amb noms en linia
Però que succeeix si posem nom a les funcions sense nom? Exemple:
var persona = {
cantar : function xiular(n){...}
}
La funció xiular no la podem cridar fora de la funció. Peró sí la podem crear dintre, es a dir, ens interesa posar nom a les funcions sense nom quan les volem fer recursives.
var persona = {
cantar : function xiular(n){ return n > 1 ? xiular(n-1) + "-fiu" : "fiu"; }
}
Memoritzar valors calculats a la propia funció (Memoize)
La memorització (memoizacion) és el pas de crear una funció que pot recordar els seus valors calculats amb anterioritat. Així es pot incrementar considerablement el rendiment evitant càlculs complexes innecessaris que ja s'han calculat. Exemple: Volem saber si un nombre és un nombre primer:
function esPrimer(valor){ if(!esPrimer.cache) esPrimer.cache = {}; if(esPrimer.cache[valor] != null) { return esPrimer.cache[valor]; } var primer = valor != 1 // 1 no pot ser mai primer for(var i = 2; i < valor; i++){ if(valor % i == 0){ primer = false; break; } } return esPrimer.cache[valor] = primer; } esPrimer(5);
Sobrecàrrega de funcions
En altres llenguatges de programació orientats a objectes, per sobrecarregar una funció se sol declarar distintes implementacions de mètodes amb el mateix nombre però amb un conjunt diferent de paràmetres. En Javascript no es fa d'aquesta manera. En Javascript es sobrecarreguen les funcions amb una única implementació que modifica el seu comportament mitjançant l'exàmen del nombre d'arguments que l'han proporcionat.
És fàcil d'imaginar que es podria implementar la sobrecàrrega de funcions utilitzant una estructura del tipus if-then i else-if. Però no sempre ho podrem fer.
Exemple: Funciò sobrecarregada de manera monolítica:
var persona = { calculMatricula = function(){ switch(arguments.length){ case 0: // fer algo break; case 1: // fer una altre cosa break; case 2: // fer una altre cosa més break; ... etc ... } } }
Veurem una tècnica per ens permet crear diverses funcions (aparentment amb el mateix nom, però es diferència pel número de paràmetres) que poden escriure's com diferents, anónimes e independents i no com un bloc monolític if-then-else-if. Tot aixó depén de una propietat poc coneguda de les funcions : la propietat length.
length de funció
El paràmetre length de funció no ha de confondre's amb la propietat length del paràmetre arguments. El paràmetre length de funció ens indica el número total de paràmetres formals amb els que s'ha declarat la funció. La propietat length del paràmetre arguments ens indica el número total de parámetres que s'han passat a la funció en el moment de cridar-la.
Exemple:
function max(a,b){
...
}
max(1,4,5,7,23,234);
En aquest cas el paràmetre length de la funció max és 2 i la propietat length del paràmetre arguments és 6.
Utilitzarem aquest paràmetre per crear funcions sobrecarregades.
function afegirMetode(objecte, nom, funcio){
var old = objecte[nom];
objecte[nom] = function(){
if(funcio.length == arguments.length)
return funcio.apply(this, arguments);
else if (typeof old == 'function')
return old.apply(this, arguments);
};
}
var persona = {};
afegirMetode(persona, "calculMatricula", function(){/* fer algo */});
afegirMetode(persona, "calculMatricula", function(a){/* fer una altre cosa */});
afegirMetode(persona, "calculMatricula", function(a,b){/* fer una altre cosa més */});
Exercici 9 : Sobrecàrrega de funcions
Donat el següent objecte:
var usuaris = {
noms : ["joan garcia", "jaume pontons", "ana riu", "marta aran", "alex fornell", "mariona cots"];
};
Crea una funció anomenada trobar que segons el número de paràmetres faci ;
- Si no es pasa cap paràmetre : Retorni directament l'array de noms.
- Si es pasa 1 paràmetre : Retorni de la llista de noms aquell nom o noms que comencin per la cadena pasada com a únic argument.
- Si es pasen 2 paràmetres : Retorni del llistat de noms aquells nom o noms que el nom sigui el primer paràmetre i el cognom el segon.
Exercici 9.1 Sobrecarga
Escrigui un programa que calculi diverses operacions, en cas de passar-li dos numeros han de tornar la divisió entre tots dos (No pot ser divisible per 0), en cas de passar-li tres paràmetres ha de tornar els dos valors d'una equació de segon grau.
Exercici 9.2 Sobrecarga
Escrigui un programa que calculi el àrea de diferents figures geomètriques (triangle, quadrat, rectangle, cercle, trapezi, etc.) sobrecarregant la funció àrea amb el número i tipus de arguments necessaris per cada tipus de figura.
Clausures
Una clausura és l'àmbit que es crea quan es declara una funció i es permet que aquesta pugui accedir i manipuli variables externes a ella, és a dir, són variables locals d'una funció mantingudes vives després que la funció hagi retornat Exemple:
var missatge = "hola";
function accedirValor(){
alert(missatge);
}
De moment, sense sorpreses. La funció accedirValor pot accedir al valor de la variable externa missatge. Aquesta variables i aquesta funció estàn definides en l'àmbit global i aquest àmbit sempre existirà mentre duri el programa. Però que pasaria si... :
var missatge = "hola";
var despres;
function accedirValor(){
var missatge_intern = "adeu";
function accedirValorInternExtern(){
alert("extern: " + missatge); //les variables es mantenen vives
alert("intern: " +missatge_intern);
}
despres = accedirValorInternExtern; //es igual a accedirValorInterneExtern();
}
accedirValor();
despres();
Conclusió:
- accedirValor funciona correctament. Están definits en l'ambit global i per tant pot accedir sempre a les variables.
- despres()...no funciona com pensàvem, oi? La variable missatge_intern està viva! Per què? per les clausures.
Quan declarem la funció accedirValorInternExtern dintre de la funció accedirValor, no estem només declarant la funció si no que estem creant una clausura que la envolta a ella i a les variables que estan en el seu àmbit en el moment de la declaració.
Podem imaginar una clausura com una bombolla segura de la funció. Les variables que estan dintre del àmbit de la funció en la seva declaració i la mateixa funció estan dintre d'aquesta bombolla. Així aquesta funció disposa de tots els elements necessaris per poder-se executar.
Anem a ampliar aquest concepte. Mireu la modificació que faig del codi anterior:
var missatge = "hola";
var despres;
function accedirValor(){
var missatge_intern = "adeu";
function accedirValorInternExtern(salutacio){
alert("extern: " + missatge);
alert("intern: " + missatge_intern);
alert("salutacio " + salutacio);
alert("fin " + fin);
}
despres = accedirValorInternExtern;
}
alert("mintern" + missatge_intern);
alert(fin);
var fin = "tard";
accedirValor();
despres("fins despres");
Conclusió: la funció interna pot veure totes les variables que hi ha en aquest codi. Però com es aixó?
Conceptes relacionats amb les clausures
- Els paràmetres de la funció s'inclouen en la clausura d'aquesta funció.
- Totes les variables del àmbit extern, incloent les que es declaren després de la declaració de la funció, estan incloses.
- Dintre del mateix àmbit, les varibles que encara no s'han definit no poden ser referenciades més endavant.
Utilitzar les clausures per crear variables privades
És un ús molt comú utilitzar les clausures per definir les variables privades. De fet, ja ho has estat fent. Exemple:
function Persona(){
var anys = 0;
this.getAnys = function(){
return anys;
};
this.envellir = function(){
anys++;
};
}
var joan = new Persona();
joan.envellir();
alert("no podem accedir: " + joan.anys);
alert("getanys: " + joan.getAnys());
Les clausures es diuen així perquè en realitat el que representen és a funcions que tenen accés a una sèrie de variables, lliures del seu propi àmbit (estan en un àmbit superior), i que fan ús d'elles mitjançant una sintaxi externa que és la que les estableix i li dóna el sentit (les tanca o clausura, d'aquí el nom).
Es veurà millor amb un exercici més apropiat com el següent codi JavaScript:
Exercici 1. Digues que mostra.
function concatenar(s1) {
return function(s2) {
return s1 + ' ' + s2;
};
}
var diHola = concatenar("Hola");
alert( diHola("visitante") );
És a dir, en la pràctica gràcies a l'ús d'una clausura hem definit una funció que permet assignar valors per a execució retardada. Això és una cosa realment útil, i no és l'única aplicació pràctica de les clausures, però gràcies a això podem habilitar tot un sistema d'execució en diferit de funcions JavaScript. Això ens permet crear punters a funcions pre-parametritzades i amb això crear un sistema d'esdeveniments, llançar accions periòdiques complexes i particularitzades amb setInterval o setTimeout ... i moltes altres coses.
Exercici 2. Digues que mostra.
function dirHola2(nom) {
var texto = 'Hola ' + nom; // local variable
var dirAlerta= function() { alert(texto); }
return dirAlerta;
}
var dir2=dirHola2('Joan');
dir2();
Exercici 3. Digues que mostra.
function dir666() {
// Variable local que acaba en la clausura
var num = 666;
var dirAlerta= function() { alert(num); }
num++;
return dirAlerta;
}
var dirNombre=dir666();
dirNombre();
Exercici 4. Digues que mostra.
var unicId = (function() {
var comptador = 0;
return function() {
return "únic-id-" + comptador++;
};
})(); // Funció que s'auto-invoca i executa automàticament
unicId();
unicId();
unicId();
Exercici 5. Digues que mostra.
var crearContador = function () {
var cuenta, f;
cuenta = 0;
f = function () {
cuenta = cuenta + 1;
return cuenta;
};
return f;
};
var contador = crearContador();
/* contador es una funcion que no recibe argumentos
y retorna una cuenta */
var a = contador();
var b = contador();
var c = contador();
var contador2 = crearContador(); // crear otro mas
var d = contador2();
var e = contador2();
var f = contador();
Una cosa útil que les clausures poden fer és guardar l'estat intern. Recorda que una clausura és creada quan una funció interna referència a una variable de la funció externa.
En aquest exemple, cada invocació d'crearContador crea un nou abast("alcance") de compte, i la clausura circumdant a la funció interna f captura aquest abast.
http://www.variablenotfound.com/2012/10/closures-en-javascript-entiendelos-de.html Enllaç Complementari
Funcions anònimes, autoexecutables i que retornen funcions
Una funció anònima es pot definir sense que sigui asiganada a cap variable:
function(qui) {
alert("hola "+qui)
}
No obstant això, fer això és completament inútil: definir una funció sense nom fa que sigui impossible ser executada més tard, ja que sense un nom amb el qual accedir-hi és impossible trobar-la. Però podem executar-la en el mateix moment en què la definim. Per fer-ho, només hem de tancar entre parèntesis, i després utilitzar uns nous parèntesi amb els paràmetres, com fem amb una funció normal.
(function() { alert("hola món") })()
Per descomptat, podem passar-li paràmetres a la nostra funció autoexecutable. En el següent exemple, es passa com a paràmetre "món" a la funció:
(function(qui) {
alert("hola "+qui)
})("món")
Pot semblar poc útil fer això ara, però més endavant veurem com és una fantàstica manera d'arreglar certs problemes.
Per descomptat, una funció pot retornar una funció anònima. Serà responsabilitat del programador assignar-la a una variable:
function saludator(qui) {
return function() {
alert("hola "+qui)
}
}
var saluda = saludator("món")
saluda()
podem executar la funció que s'ha retornat directament, sense assignar-la a cap variable:
function saludator(qui) {
return function() {
alert("hola "+qui)
}
}
saludator("món")()
És clar, ningú et impedeix sobreescriure una funció amb una altra.
function saludator(qui) {
return function() {
alert("hola "+qui)
}
}
saludator = saludator("món")
saludator()
Aquí, la primera vegada que executem saludator ("món") ens retorna una funció anònima (que mostra "hola món"). Aquesta funció és assignada a la variable saludator, de manera que la segona vegada que truquem saludator (), estem executant la nova funció anònima (la del "hola món"), i la funció inicial original es perd per sempre.
Funcions immediates
Una Funció immediata es basa en el concepte de les clausures. Exemple de funció immediata:
(function(){})()
Analitzarem la construcció de la funció ignorant el primer grup de paréntesis.
(....)()
Sabem que podem fer la crida d'una funció utilitzant la sintaxis functionName(), però en lloc del nom podem utilitzar qualsevol expressió que es refereixi a una de les seves instàncies.
var algunaFuncio = function(){...};
result = algunaFuncio();
// o també podem fer:
result = (algunaFuncio)();
Aixó significa que (---)(), el primer joc de parèntesis és un delimitador que tanca una expressió. El segon lloc de paréntesis és un operador.
Ara en lloc d'una variable, posem la funció anónima directament.
(function(){...})();
Que fa aquesta funció?
- Crea una instància de la funció
- Executa la funció
- Descarta la funció
Per què és útil? Podem crear un àmbit temporal que emmagatzemi el nostre estat.
(function(){
var numclicks = 0;
document.addEventListener("click", function(){alert(++numclicks);}, false);
})();
Lo important és observar que es crea una clausura pel controlador que inclou numclicks, llavors només ell pot fer referència a aquesta variable. Ningú més podrá modificar el seu valor. Aquesta és una de les formes d'ús comú de les funcions immediates: com envoltoris simples e independents.
Es poden passar paràmetres a les funcions immediates? Exemple:
(function(salutacio){alert(salutacio);})("Hola");
Les funcions aninades
Les funcions que es troben dintre de altres funcions s'anomenen internes perque només podem accedir a elles dintre del codi de la funció que la engloba. A aquesta estructura l'anomenen funcions niades. Exemple:
function saluda(quien) {
function alertasaludo(quien) {
alert("hola "+quien)
}
alertasaludo(quien)
}
saluda("mundo")
Podem combinar funcions niades amb funcions que retornen funcions:
function saludator(quien) {
function alertasaludo() {
alert("hola "+quien)
}
return alertasaludo
}
var saluda = saludator("mundo")
saluda()
I lo anterior ho podem combinar amb funcions anónimes i auto-executables:
var saluda = (function(quien) {
function alertasaludo() {
alert("hola "+quien)
}
return alertasaludo
})("mundo")
saluda();
Exemple del seu ús:
var parOimpar = (function() {
var hoy = new Date()
if (new Date().getDate() % 2 == 0) {
return function() { alert("hoy es dia par") }
} else {
return function() { alert("hoy es dia impar") }
}
})()
parOimpar();
Exercicis
Exercici YAHOO
Els Yahoo compten amb els dits : un, dos, tres i quatre. Coneixen el cero, pero no les quantitats negatives, per a ells son quantitats desconegudes. Conscients de la seva ignorància, sospiten de la existència de una quantitat més gran que quatre: molts.
a) Funció següent :
Per pode comptar, els yahoo coneixen la operació següent que transforma una quantitat en una altre de la següent manera :
– El següent de lo desconegut continua sent desconegut
– Per a les altres quantitats de zero a quatre, el següent és de un a molts respectivament.
– Per últim, el següent a molts és també molts.
Crea el métode següent de la classe Yahoo :
Function Yahoo(){
var numeros = ....
this.seguent = function(numero){....}
}
// utilització del mètode:
var yahoo1 = new Yahoo();
alert(yahoo1.seguent(“desconegut”)); // ha de mostrar l'alert : 'desconegut'
alert(yahoo1.seguent(“cero”)); // ha de mostrar l'alert : 'un'
alert(yahoo1.seguent(“quatre”)); // ha de mostrar l'alert : 'molts'
alert(yahoo1.seguent(“molts”)); // ha de mostrar l'alert : 'molts'
b) Funció suma :
Els yahoos tenen un mètode suma que funciona de la següent manera:
– suma() --> retorna el valor desconegut
– suma(x) --> retorna el valor x (x pot ser zero, un, dos, tres, quatre, molts o desconegut)
– suma(numero, x)
si numero és desconegut, retorna desconegut sigui quin sigui x
si numero és zero, retorna lo mateix que suma(x)
si numero és uno, retorna siguiente(x)
si numero és dos, retorna siguiente(siguiente(x))
.....
si numero és molts, retorna molts sigui quin sigui x.
Exercici RELLOTGE
http://www.w3schools.com/js/js_obj_date.asp
http://www.w3schools.com/jsref/jsref_obj_date.asp
Crea una classe anomenada Rellotge. Aquesta classe ha de tenir les següents funcions:
– Constructor Rellotge : Posa en marxa el rellotge
– GetTime : Retorna el temps en segons des de que es va crear el rellotge
– StopTime: Para el rellotge però emmagatzema els segons que ha estat funcionant.
– StartTime: Torna a posar en marxa el rellotge. El temps que ha estat parat no s'ha de tenir en compte al calcular el temps.
– setFunctionOnTime(time, function) : Emmagatzema una funció per a executar-la com a molt d'hora en el numero de segons que indiqui time.
– Execute : Executa totes les funcions, una darrera l'altre, que estan emmagatzemades si el seu time es inferior o igual que el temps actual, es a dir, el temps en segons que ha passat des de que es va posar en marxa.
Crea un programa on es pugui veure que aquesta classe funciona correctament.
Bibliografia
John Resig, Bear Bibeault, "Secrets of the Javascript Ninja", Manning Publications, 2012. ISBN 193398869X