NF1 - Estructures definides pel Programador - Funcions

De wikiserver
La revisió el 21:55, 1 oct 2015 per Asalinas (Discussió | contribucions) (Exercici 7)
Dreceres ràpides: navegació, cerca

Funcions

Comprovació i Depuració (no entra a l'examen)

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.

/*Activa el depurador i ves a la "consola" a vore que mostra*/
a = 5;
b = 6;
c = a + b;
console.log(c);

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.15.0.css" type="text/css" rel="stylesheet" />
		<script src="qunit-1.15.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ó.


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

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.

solucio framework tests unitaris

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

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.

Solucio this
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

albertovilches

msdn-firefox