NF1 - Estructures definides pel Programador - Funcions
Contingut
- 1 Funcions
- 1.1 Comprovació i Depuració
- 1.2 Les Funcions son fonamentals
- 1.3 Var vs Let
- 1.4 Les funcions sense nom
- 1.5 Sobrecàrrega de funcions
- 1.6 Clausures
- 1.7 Més Exemples de Clausures
- 1.8 Funcions anònimes, autoexecutables i que retornen funcions
- 1.9 Funcions immediates
- 1.10 Les funcions aninades
- 1.11 Exercicis
- 2 Bibliografia
Funcions
http://es6-features.org/ desestructuración. spread vs rest
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.
/*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:
EJEMPLO 1
<html>
<head>
<meta charset = "utf-8">
<title>QUnit basic example</title>
<link rel = "stylesheet" href = "https://code.jquery.com/qunit/qunit-1.22.0.css">
<script src = "https://code.jquery.com/qunit/qunit-1.22.0.js"></script>
</head>
<body>
<div id = "qunit"></div>
<div id = "qunit-fixture"></div>
<script>
function square(x) {
return x * x;
}
QUnit.test( "TestSquare", function( assert ) {
var result = square(2);
assert.equal( result, "4", "square(2) should be 4." );
});
</script>
</body>
</html>
EJERCICIO
Se desea hacer un test de pruebas con la función ponerAlRevés, por ello deberá ponerse valores negativos, positivos, float, palabra... y las cadenas con alguna coma, mayúscula.
SOLUCIÓ:
function posarReves(opcio, text) {
var separator = " ";
if(opcio == 0){
separator = "";
}
return text.split(separator).reverse().join(separator);
}
JEST
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:
//qunit
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);
});
//Mediante Consola
console.log(typeof window.lleugera === "function"); //true
console.log(lleugera.name === "lleugera"); //true
console.log(lleugera.name === "lleugera"); //true
console.log(typeof window.sensenom === "function"); //true
console.log(sensenom.name === ""); //false
console.log(typeof window.esVeritat === "function"); //true
console.log(typeof window.externa === "function"); //true
console.log(typeof window.interna === "function"); //false
console.log(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.
Arrow =>
¿Cuántas veces has programado un código con una estructura similar a la siguiente?
// ES5
// Imaginemos una variable data que incluye un array de objectos
var data = [{'nombre':"julio","apellido":"noguera"}, {"nombre":"albert","apellido":"canela"}, {"nombre":"alex","apellido":"salinas"}];
data.forEach(function(i){
console.log(i);
});
Con la función arrow => de ES6, el código anterior se sustituiría por:
//ES6
var data = [{'nombre':"julio","apellido":"noguera"}, {"nombre":"albert","apellido":"canela"}, {"nombre":"alex","apellido":"salinas"}];
data.forEach(i => console.log(i));
});
Mucho más limpio y claro. CoffeeScript (un metalenguaje que compila a JavaScript) usa algo parecido.
Incluso la podemos utilizar así:
// ES5
var miFuncion = function(num) {
return num + num;
}
// ES6
var miFuncion = (num) => num + num;
El mètode .map()
La funció map() s'aplica a tots els elements del vector, de maneta similar a forEach():
var vect = [1,2,3,4,5];
vect.map(function(i){
return i+5;
})
que retorna: [ 6, 7, 8, 9, 10 ]
El vector inicial no es modifica, ie, map() no és destructiva. Si imprimim el vector de nou:
vect
[ 1, 2, 3, 4, 5 ]
Per tant, no podem modificar el vector directament amb coses com:
var vect = [1,2,3,4,5];
vect.map(function(i){
i=i+5;
})
que retorna:
[ undefined, undefined, undefined, undefined, undefined ]
vect
[ 1, 2, 3, 4, 5 ]
Però sí podem modificar el vector inicial si li assignem el retorn del mapeig:
var vect = [1,2,3,4,5];
vect = vect.map(function(i){
return i+5;
})
/*
var vect = [1,2,3,4,5];
vect = vect.map( i => i+5);
*/
que retorna:
[ 6, 7, 8, 9, 10 ]
però ara vect val:
[ 6, 7, 8, 9, 10 ]
El mètode .filter()
És una funció que s'aplica a tots els elements del vector, i només retorna aquells que compleixen la condició. Exemple:
var vect = [1,2,3,4,5];
var resul=vect.filter(function(i){
if(i>3)
return i;
})
/*const words = ['spray', 'limit', 'elite', 'exuberant', 'destruction', 'present'];
const result = words.filter(word => word.length > 6);
//devuelve Array ["exuberant", "destruction", "present"]
*/
que retorna:
resul=[ 4, 5 ]
map, arrow,filter, reduce, findIndex
Dado un objeto
posts = [
{id: 1, upVotes: 2},
{id: 2, upVotes: 18},
{id: 3, upVotes: 1},
{id: 4, upVotes: 30},
{id: 5, upVotes: 50}
];
Se desea hacer un benchmark para comprobar la velocidad de " for, foreach, map, filter, reduce..." por tanto se usará console.time('ejemplo') para establecer el tiempo de inicio y console.timeEnd('ejemplo') para finalizar el cronómetro.
Ejercicio test de pruebas
Solució
https://hackernoon.com/javascript-performance-test-for-vs-for-each-vs-map-reduce-filter-find-32c1113f19d7 https://medium.com/@joomiguelcunha/learn-map-filter-and-reduce-in-javascript-ea59009593c4
http://blog.builtbyedgar.com/javascript-map-filter-y-reduce-para-dummies/
À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.
Més exemples
//Global- Podemos accedes a ellas desde cualquier parte del cogido
//Local - Variables creadas dentro de una función solo pueden ser accedidas, desde su función o una función anidada
var variableGlobal = "variable Global";
var miFuncion = function () {
var variableLocal = "variable Local";
var funcionLocal = function () {
var variableLocal = "esta variable es local, dentro de funcionLocal";
alert(variableLocal);
};
funcionLocal();
alert(variableLocal); //variable Local
}
miFuncion();
alert(variableLocal); //falla
À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
http://www.webimes.es/blog/ambito-variables-javascript/
Var vs Let
JAVA
public static void main(String[] args) {
int a = 1;
int b = 2;
{
int c = 3;
}
System.out.println(a +" "+ b +" "+ c);
// Error de compilación: c cannot be resolved to a variable
}
JAVASCRIPT
var a = 1;
var b = 2;
{
var c = 3;
}
console.info(a, b, c); // 1 2 3
LET
let a = 1;
let b = 2;
{
let c = 3;
}
console.info(a, b, c); // ReferenceError: c is not defined
VAR
En resumen, en ES5 (Javascript más tradicional) no se permitía declarar un alcance más acotado, como las llaves de una condición if, un bucle etc. Veamos un ejemplo:
function miFuncion() {
console.log(miVar); //undefined
if (true) {
var miVar = "Hola mundo";
}
console.log(miVar); //Hola mundo
};
En este caso, pensando en ES5, la variable "miVar" está definida dentro de un bloque if, pero aun así su ámbito será el de la función miFuncion().
LET
Para solucionar todo esto en Javascript ES6 se ha implementado alguna forma nueva de declarar variables que ahora será la más indicada en la mayoría de los casos. Se trata de la construcción "let" que veremos mejor con un ejemplo similar:
function () {
console.log(miVar); //undefined
if (true) {
let miVar = "Hola mundo";
}
console.log(miVar); //undefined
};
La clave de let es que restringe su existencia al ámbito donde ha sido declarada (cualquier ámbito expresado con unas llaves).
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
http://www.etnassoft.com/2012/01/12/el-valor-de-this-en-javascript-como-manejarlo-correctamente/
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"; //la variable pertenece a window
var myApp = function(){
var name = "World"
var sayHello = function(){
console.log( 'Hello, ' + this.name ); //this pertenece a window
};
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.
solució
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
}
solució
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.log(this);
function test(){
console.log(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.3.1
var obj = {
name: 'obj',
run: function () {
this.value = 1;
console.log(this);
(function () { // se crea un nuevo scope
console.log(this);
})();
that=this;
function test(t) { // se crea un nuevo scope
console.log(t);
}
test(that);
}
};
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"; }
}
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 ...
}
}
}
Sin pasar argumentos
function suma(){
switch(arguments.length){
case 0:
console.log("cero");
break;
case 1:
console.log(arguments[0]);
break;
case 2:
console.log(arguments[0]+arguments[1]);
break;
}
}
suma();
suma(2);
suma(3,4)
SOLUCIÓ
Tenemos un array con una serie de nombre y apellidos ["joan garcia", "jaume pontons", "ana riu", "marta aran", "alex fornell", "mariona cots"], en caso de no pasar ningún argumento devolverá el array, si pasamos un trozo de cadena y lo encuentra, deberá añadirlo en un nuevo array y devolverlo, en caso de pasar 2 parámetros (nom, apellidos) deberá encontrarlos e introducirlos en el nuevo array.
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 aquells noms que comencin per la cadena pasada com a únic argument.
- Si es pasen 2 paràmetres : Retorni de la llista de noms aquells noms que el nom sigui el primer paràmetre i el cognom el segon.
SOLUCIÓ
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
https://medium.com/@sergiodxa/definiendo-conceptos-closure-y-scope-en-javascript-9081f1e113e6 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");
Aquí estas creando una clausura. La creas en el momento que declaras una función dentro otra. Y permanecerá viva mientras viva la función declarada internamente.
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.
Més Exemples de Clausures
Supone el siguiente código, el ejemplo mas simple que se me ocurre.
function saltos(valor) {
var acumulado = 0;
return function() {
acumulado += valor;
return acumulado;
};
}
Aquí estas creando una clausura. La creas en el momento que declaras una función dentro otra. Y permanecerá viva mientras viva la función declarada internamente.
var quintos = saltos(5);
quintos(); // retorna 5
quintos(); // retorna 10
quintos(); // retorna 15
// y asi sigue... 20, 25, 30..
En definitiva, incluso después de que termina la ejecución de saltos(5), JavaScript mantiene una referencia a las variables declaradas en saltos (acumulado y valor), visibles para la función creada en el interior.
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();
console.log(a);
var b = contador();
console.log(b);
var c = contador();
console.log(c);
var contador2 = crearContador(); // crear otro mas
var d = contador2();
console.log(d);
var e = contador2();
console.log(e);
var f = contador2();
console.log(f);
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
Tracta d'evitar utilitzar variables globals, per temes de securetat. Per exemple, una variable global llistaUsuaris.
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();
function funcion1(){
let funcion1 ="funcion1";
function funcion2(){
let funcion2="funcion2";
function funcion3(){
return "Solución";
}
return funcion3;
}
return funcion2;
}
var resultado1= funcion1();
var resultado2= resultado1();
var resultado= resultado2();
var otraForma= funcion1()()();
Exercicis
Bibliografia
John Resig, Bear Bibeault, "Secrets of the Javascript Ninja", Manning Publications, 2012. ISBN 193398869X