NF1 - Estructures definides pel Programador - Funcions

De wikiserver
La revisió el 18:30, 22 set 2014 per Jnoguera (Discussió | contribucions) (Exercici 9 : Sobrecàrrega de funcions)
Dreceres ràpides: navegació, cerca

Funcions

Comprovació i Depuració

Crear conjunts de proves efectius per al codi sempre és important, per això ho tractem ara, abans de continuar amb les funcions. En Javascript no solament ens trobem amb els típics problemes per garantir la qualitat sinó que també ens trobem amb obstacles a l'hora de determinar si el nostre codi funciona en tots els navegadors amb els quals hem decidit ser compatibles.

Hi ha dos importants enfocaments per depurar Javascript: el registre i els punts de parada. Tots dos són útils per respondre a la pregunta crucial, "Què està passant amb el meu codi?".

Registre

Les declaracions de registre, com les quals fem en executar console.log(), són part del codi (encara que sigui de forma temporal) i resulten pràctiques en un entorn multinavegador. Les consoles del navegador han millorat molt el procés de registre mes allà de la tècnica d'afegir una alerta. Podem escriure totes les nostres declaracions de registre en la consola i accedir a elles de forma immediata o amb posterioritat sense afectar al flux normal del programa, alguna cosa que no és possible amb alert().

El registre és perfecte per buscar quin pot ser l'estat de les coses mentre s'està executant el codi però, en algunes ocasions, volem detenir l'acció i fer un cop d'ull. Aquí és on apareixen els punts de parada.

Punts de parada

Detenen l'execució d'un comando en una línia de codi concreta, detenint el navegador. Això ens permet investigar sense presses l'estat de tot tipus de coses en el punt de parada, incloent totes les variables accessibles, el context i la cadena d'abast.

Generació de proves

Les bones proves exhibeixen tres importants característiques:

  • Repetibilitat: Els resultats han de ser molt fàcils de reproduir. Les proves que s'executen en repetides ocasions sempre haurien de produir exactament els mateixos resultats. Les proves no depenen de factors externs com la xarxa o la CPU.
  • Simplicitat: Les proves han de centrar-se a comprovar un únic element. Hem de llevar el CSS i l'HTML com sigui possible sense afectar la intenció del cas de prova.
  • Independència: Les proves han d'executar-se en solitari. Cal evitar que els resultats d'una prova depenguin d'una altra.

Enfocaments per crear proves:

  • Deconstructius: El codi existent es descompon per aïllar un problema, eliminant alguna cosa que no està relacionat amb la qüestió.
  • Constructius: Comencem a partir d'un cas reduït ben conegut i creguem fins que som capaços de reproduir l'error en qüestió.

Exemple de cas de prova DOM empleat per comprovar JQuery:

<script src="dist/jquery.js"></script>
<script>
  $(document).ready(function() {
    $("#test").append("test");
  });
</script>
<style>
  #test { width: 100px; height: 100px; background: red; }
</style>
<div id="test"></div>

Entorns de comprovació

Aquesta eina serveix a una necessitat única: mostrar els resultats de les proves, fent que sigui més fàcil determinar quins han sortit bé i quins han fallat. Els entorns de comprovació ens ajuden a aconseguir aquest objectiu sense haver de preocupar-nos per una altra cosa que no sigui crear les proves i organitzar-les en conjunts. Els entorns de comprovació única de Javascript solen proporcionar alguns components clau:

  • un executor de proves,
  • agrupacions de proves i
  • aserveracions.

Alguns ofereixen a més l'habilitat d'execució asincrónica.

QUnit

És l'entorn que va ser creat per provar jQuery. Ha sobrepassat les seves metes inicials i ara s'ha convertit en una utilitat independent. QUnit es va dissenyar bàsicament per ser una solució senzilla, proporcionant una API mínima però fàcil d'utilitzar. Característiques:

  • API senzilla
  • compatible amb la comprovació asincrónica.
  • No es limita a jQuery

web: http://qunitjs.com/

Exemple d'ús:

Primer creem un còpia del codi a testar. Per exemple creem el fitxer exercici5.js on tenim la solució del exercici 5 de la unitat anterior. L'exercici consisteix a posar al revés un text donat.

--- Contingut del fitxer exercici5.js ---
function posarReves(opcio, text) {
	var separator = " ";
	if(opcio == 0){
		separator = "";
	}
	return text.split(separator).reverse().join(separator);
}

Segon creem un joc de proves per aquest exercici. Per exemple podem crear un fitxer anomenat jocProvesEx5.js El contingut del fitxer pot ser el següent:

test("Conjunt de Tests", function() {
	equal(posarReves("333", "hola a tots"), false);
	equal(posarReves("huksi", "hola a tots"), false);
	equal(posarReves(-2, "hola a tots"), false);
	equal(posarReves(0, "hola, a tots"), "stot a ,aloh");
	equal(posarReves(0, "hola a tots"), "stot a aloh");
	equal(posarReves("dead", "hola a tots"), false);
	equal(posarReves(1, "hola, a tots"), "tots a hola,");
	equal(posarReves(1, "hola a tots"), "tots a hola");
});

Tercer creem el HTML on combinem el codi a provar, els tests i les llibreries de QUnit.

<!DOCTYPE html>
<html>
	<head> 
		<meta http-equiv="Content-Type" content="text/html" charset=utf-8" />
		<title>Tests per al exercici 5</title>		
		<link href="qunit-1.12.0.css" type="text/css" rel="stylesheet" />
		<script src="qunit-1.12.0.js"></script>
		<script src="jocProvesEx5.js"></script>
		<script src="exercici5.js"></script>
	</head>
	<body>
		<div id="qunit"></div>	
	</body>
</html>

Gràcies a aquest conjunt de proves podem veure que ens fa falta una funció de detecció d'errors més acurada sobre la variable opcio.

YUI Test

És un entorn de comprovació creat per Yahoo! Proporciona un nombre impressionant de característiques i funcionalitats que garanteixen la cobertura de qualsevol cas de comprovació única. Es distingeix per:

  • Àmplia i exhaustiva funcionalitat
  • És compatible amb la comprovació asincrónica
  • Bona simulació d'esdeveniments.

web : http://yuilibrary.com/projects/yuitest/

JsUnit

És el més antic en termes d'edat de la base de codi i qualitat. Ve del popular entorn de comprovació Java JUnit per Javascript. L'entorn no s'ha actualitzat molt recentment i per això pot ser que no sigui la millor opció per treballar amb els navegadors moderns. web: https://github.com/pivotal/jsunit Existeix una versió que s'està implementant per donar suport als nous navegadors que es diu Jasmine: web: https://github.com/pivotal/jasmine

Exercicis

Exercici 1

Crea un joc de proves per al Exercici 3 DNI. Canvia els alerts de comprovació, si tens, per console.log(). Utilitza el framework QUnit.

Exercici 2

Crea un joc de proves per al Exercici 7 Nombres Romans.Canvia els alerts de comprovació, si tens, per console.log(). Utilitza el framework QUnit.

Exercici 3

Unifica els dos jocs de proves anteriors en un mateix fitxer. Es a dir, només has de tindre els següents fitxers: Fitxer HTML, Fitxer qunit-1.12.0.css, Fitxer qunit-1.12.0.js, Fitxer JocProvesUnificat.js, Fitxer Exercicis.js

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

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 funcions

En Javascript els àmbits actuen de forma una mica diferent que en la majoria de llenguatges, la sintaxi dels quals està influenciada per C. En concret, els que utilitzen { i } com a delimitadors de bloc. En la majoria d'aquests llenguatges, cada bloc crea el seu propi àmbit. Això no ocorre amb Javascript.

En javascript, les funcions són les que declaren els àmbits i no els blocs. L'àmbit d'una declaració creada dins d'un bloc no acaba (com ocorre en altres llenguatges) al final del bloc.

Exemple:

if(window){
	var x = 213;
}
alert(x);

En la majoria de llenguatges esperem que l'àmbit de la declaració de x acabi al final del bloc creat per la sentència if i que l'alerta falli en un valor no definit. Però no ocorre així. El valor del alert és 213 perquè javascript no acaba els àmbits al final dels blocs.

Regles per establir un àmbit:

  • 1. Les declaracions de variables estan en l'àmbit des del punt de declaració fins al final de la funció en la qual es declaren, amb independència de la incorporació del bloc
  • 2. Les funcions amb nom estan en l'àmbit de tota la funció en la qual es declaren, amb independència de la incorporació del bloc.
  • 3. Per als àmbits de declaració, el context global actua com una gran funció que inclou el codi de la pàgina.

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

solucio ambits amb test unitaris

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.

Exercici 7

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
}
solucio funcio com a metode

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.

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: aply() 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 8

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.


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. 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);
		alert("intern: " +missatge_intern);
	}
	despres = accedirValorInternExtern;
}

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); // undefined!!
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());

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 autoexecutables

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();


Bibliografia

John Resig, Bear Bibeault, "Secrets of the Javascript Ninja", Manning Publications, 2012. ISBN 193398869X

albertovilches

msdn-firefox