5. Objetos
!
Dinámicos
!
Set de strings
var obj = {};
obj.nuevaPropiedad = 1;
delete obj.nuevaPropiedad;
var strset = {
hola: true,
adios: true
};
"hola" in strset;
6. Objetos
!
Referencias
var p1 = {x: 1},
p2 = p1;
p1 === p2; // true
p1.x = 5;
p2.x; // 5
!
!
Todo son objetos excepto: strings, números,
booleans, null o undefined
Strings, números y booleans se comportan como
objetos inmutables
7. Objetos
!
!
Ciudadanos de primer
orden
Contener valor
primitivo u otros
objetos. Incluyendo
funciones.
(function (obj) {
return {b: 2};
})({a: 1});
var obj = {
f: function() {
console.log("hola");
}
};
obj.f();
9. Clases
!
Pueden entenderse como:
−
Tipo (jerárquico) de datos
−
Aquí no
−
Categoría de objetos con la misma estructura
−
Al grano: objetos con el mismo prototipo.
10. Clases
Si esto es un “punto”
var point = {x: 0, y: 0};
Y esto es otro “punto”
var point2 = {x: 5, y: 5};
¿Qué es esto?
var what = {x: 10, y: 10};
¿Y esto?
var isit = {x: -10, y: -20};
13. Mensajes
• Teniendo:
var obj = {
nombre: "Pepito",
saludo: function () {
return "Hola, Mundo!";
}
};
• ¿Y esto otro? (¡cuidado!)
obj[“saludo”]();
14. Mensajes
• Teniendo:
var obj = {
nombre: "Pepito",
saludo: function () {
return "Hola, Mundo!";
}
};
• ¿Es lo mismo?
var fn = obj["saludo"];
fn();
15. Mensajes
• Teniendo:
var obj = {
nombre: "Pepito",
saludo: function () {
return "Hola, Mundo!";
}
};
• ¡NO es no mismo!
var fn = obj["saludo"];
fn();
16. Mensajes
• Una función se puede ejecutar de 4 maneras:
- Invocando directamente la función
(function() { alert("Hey!"); })();
- Enviando un mensaje a un objeto (método)
objeto.metodo();
- Como constructor
new MiConstructor();
- Indirectamente, a través de call(...)
fn.call({}, "param");
y
apply(...)
17. Mensajes
- Invocando directamente la función
(function() { alert("Hey!"); })();
- Enviando un mensaje a un objeto (método)
objeto.metodo();
18. Mensajes
Un mensaje se envía a un receptor
var obj = {};
obj.toString(); // [object Object]
"hola don Pepito".toUpperCase();
19. Mensajes
Un mensaje se envía a un receptor
var obj = {};
obj.toString(); // [object Object]
"hola don Pepito".toUpperCase();
20. Mensajes
La sintaxis es engañosa
var obj = {
coleccion: ["uno", "dos", "tres"],
metodo: function() {
return "Hola, Mundo!";
}
};
obj.coleccion[1];
vs.
obj.metodo();
22. Mensajes
var fn = obj.metodo;
fn();
- Accede a la propiedad
“metodo” de obj
- Supongo que es una función
y la invoco
obj.metodo();
23. Mensajes
var fn = obj.metodo;
fn();
obj.metodo();
- Accede a la propiedad
- Envía el mensaje “metodo” a
- Supongo que es una función
- Si existe, obj se encarga de
“metodo” de obj
y la invoco
obj
ejecutar la función
24. Mensajes
var fn = obj.metodo;
fn();
obj.metodo();
- Accede a la propiedad
- Envía el mensaje “metodo” a
- Supongo que es una función
- Si existe, obj se encarga de
- NO HAY RECEPTOR
- obj ES EL RECEPTOR
“metodo” de obj
y la invoco
obj
ejecutar la función
27. Mensajes
Un error típico:
$("#elemento").click(objeto.clickHandler);
• Lo que se intenta decir:
- “Al hacer click sobre #elemento, envía el mensaje
clickHandler a objeto”
• Lo que se dice en realidad:
- “Accede al valor de la propiedad clickHandler de objeto y
ejecútalo al hacer click sobre #elemento”
29. El receptor: this
¿Por qué tanto lío con el receptor del mensaje?
- ¡El receptor es this!
- La metáfora mensaje/receptor aclara su (escurridizo)
significado
30. El receptor: this
this = “el receptor de este mensaje”
var nombre = "Sonia";
var obj = {
nombre: "Pepito",
saludo: function() {
alert("hola " + this.nombre);
}
}
obj.saludo();
31. El receptor: this
this
• Su significado es dinámico
• Se decide en el momento (y según la manera) de
ejecutar la función
• Se suele llamar “el contexto de la función”
• Cuando no hay receptor, apunta al objeto global
32. El receptor: this
Cuando no hay receptor, es el objeto global
var nombre = "Sonia"
var obj = {
nombre: "Pepito",
saludo: function() {
alert("hola " + this.nombre)
}
}
var fn = obj["saludo"];
fn();
33. El receptor: this
Su valor es dinámico
var obj = {
nombre: "Pepito",
saludo: function() {
alert("hola " + this.nombre);
}
};
var maria = {
nombre: "María"
};
maria.saludo = obj.saludo;
maria.saludo();
34. El receptor: this
Semánticamente, es como un parámetro oculto
function ([this]) {
alert("hola " + this.nombre);
}
que el receptor se encargara de proveer
obj.saludo(); => saludo([obj]);
35. El receptor: this
Semánticamente, es como un parámetro oculto
var nombre = "Sonia";
var obj = {
nombre: "Pepito",
saludo: function() {
var saludo_fn = function() {
alert("hola " + this.nombre);
};
saludo_fn();
}
};
obj.saludo();
36. El receptor: this
Semánticamente, es como un parámetro oculto
var nombre = "Sonia";
var obj = {
nombre: "Pepito",
saludo: function([this]) {
var saludo_fn = function([this]) {
alert("hola " + this.nombre);
};
saludo_fn([objeto global]);
}
};
obj.saludo([obj]);
37. El receptor: this
Semánticamente, es como un parámetro oculto
var nombre = "Sonia";
var obj = {
nombre: "Pepito",
saludo: function([this]) {
var saludo_fn = function([this]) {
alert("hola " + this.nombre);
};
saludo_fn([objeto global]);
}
};
obj.saludo([obj]);
38. El receptor: this
Es decir:
• Cada función tiene su propio this
• Una función anidada en otra NO comparte el receptor
• El valor de this depende de la invocación, NO de la
definición (no se clausura)
39. El receptor: this
Otro error común:
var obj = {
clicks: 0,
init: function() {
$("#element").click(function() {
this.clicks += 1;
});
}
};
obj.init();
40. El receptor: this
Otro error común:
var obj = {
clicks: 0,
init: function([this]) {
$("#element").click(function([this]) {
this.clicks += 1;
});
}
};
obj.init([obj]);
41. El receptor: this
Una posible solución (aunque no la mejor):
var obj = {
clicks: 0,
init: function() {
var that = this;
$("#element").click(function() {
that.clicks += 1;
});
}
};
obj.init();
42. Repaso: Mensajes
• Una función se puede ejecutar de 4 maneras:
- Invocando directamente la función
(function() { alert("Hey!"); })();
- Enviando un mensaje a un objeto (método)
objeto.metodo();
- Como constructor
new MiConstructor();
- Indirectamente, a través de call(...)
fn.call({}, "param");
y
apply(...)
43. Repaso: Mensajes
Una función se puede ejecutar de 4 maneras:
Invocando directamente la función
Enviando un mensaje a un objeto (método)
Como constructor
- Indirectamente, a través de call(...)
fn.call({}, "param");
y
apply(...)
44. El receptor: this
• Las funciones son objetos
• Se pueden manipular como cualquier otro objeto
- Asignar valores a propiedades
- Pasar como parámetros a otras funciones
- Ser el valor de retorno
- Guardarse en variables u otros objetos
• Tienen métodos
var fn = function() { alert("Hey!"); };
fn.toString();
45. El receptor: this
• Dos métodos permiten manipular el receptor
(contexto):
- fn.call(context
[, arg1 [, arg2 [...]]])
var a = [1,2,3];
Array.prototype.slice.call(a, 1, 2); // [2]
- fn.apply(context,
arglist)
var a = [1,2,3];
Array.prototype.slice.apply(a, [1, 2]); // [2]
46. El receptor: this
var nombre = "Objeto Global";
function saluda() {
alert("Hola! Soy " + this.nombre);
}
var alicia = {
nombre: "Alicia"
};
saluda();
saluda.call(alicia);
47. arguments
• El otro parámetro oculto
• Contiene una lista de todos los argumentos
• NO es un Array
function echoArgs() {
alert(arguments); // [object Arguments]
}
echoArgs(1, 2, 3, 4);
48. arguments
• Se comporta (más o menos) como Array...
function echoArgs() {
alert(arguments[0]); // 1
}
echoArgs(1, 2, 3, 4);
• ...pero NO del todo
function echoArgs() {
return arguments.slice(0, 1); // Error!
}
echoArgs(1, 2, 3, 4);
49. arguments
• Un truco:
function echoArgs() {
var slice = Array.prototype.slice;
return slice.call(arguments, 0, 1);
}
echoArgs(1, 2, 3, 4); // [1]
50. arguments
¡Cuidado, se comporta como parámetro oculto!
function exterior() {
var interior = function() {
alert(arguments.length);
};
interior();
}
exterior("a", "b", "c");
51. intermedio: this y arguments
¿Qué hace esta función?
function misterio(ctx, fn) {
return function() {
return fn.apply(ctx, arguments);
}
}
52. Intermedio: this y arguments
function misterio(ctx, fn) {
return function() {
return fn.apply(ctx, arguments);
}
}
var algo = misterio();
typeof algo; // ???
53. Intermedio: this y arguments
function misterio(ctx, fn) {
return function() {
return fn.apply(ctx, arguments);
}
}
var algo = misterio();
algo(); // ???
54. Intermedio: this y arguments
function misterio(ctx, fn) {
return function() {
return fn.apply(ctx, arguments);
}
}
var algo = misterio({}, function() {
return this;
});
typeof algo(); // ???
55. Intermedio: this y arguments
function misterio(ctx, fn) {
return function() {
return fn.apply(ctx, arguments);
}
}
var obj = {};
var algo = misterio(obj, function() {
return this;
});
obj === algo(); // ???
56. Intermedio: this y arguments
function misterio(ctx, fn) {
return function() {
return fn.apply(ctx, arguments);
}
}
var obj = {};
var algo = misterio({}, function() {
return this;
});
obj === algo(); // ???
57. Intermedio: this y arguments
function misterio(ctx, fn) {
return function() {
return fn.apply(ctx, arguments);
}
}
var obj = {
nombre: "Bárbara"
};
var algo = misterio(obj, function() {
return this.nombre;
});
algo(); /// ???
58. Intermedio: this y arguments
function misterio(ctx, fn) {
return function() {
return fn.apply(ctx, arguments);
}
}
var obj = {
nombre: "Bárbara"
};
var algo = misterio(obj, function (saludo) {
return saludo + " " + this.nombre;
});
algo("Hola, "); /// ???
59. Intermedio: this y arguments
function misterio(ctx, fn) {
return function() {
return fn.apply(ctx, arguments);
}
}
var barbara = { nombre: "Bárbara" };
var carlos = { nombre: "Carlos" };
var algo = misterio(barbara, function (saludo) {
return saludo + " " + this.nombre;
});
algo.call(carlos, "Hola, "); /// ???
60. Intermedio: this y arguments
•
bind: fija una función a un contexto
function bind(ctx, fn) {
return function() {
return fn.apply(ctx, arguments);
}
}
61. Intermedio: this y arguments
Volviendo al problema:
var obj = {
clicks: 0,
init: function() {
$("#element").click(function() {
// MAL
this.clicks += 1;
});
}
};
obj.init();
62. Intermedio: this y arguments
Apaño:
var obj = {
clicks: 0,
init: function() {
var that = this;
$("#element").click(function() {
that.clicks += 1;
});
}
};
obj.init();
63. Intermedio: this y arguments
¿Qué pasa si el callback es un método?
var obj = {
clicks: 0,
incClicks: function() {
this.clicks += 1;
},
init: function() {
$("#element").click(
// ???
);
}
};
obj.init();
64. Intermedio: this y arguments
Apaño cada vez más feo:
var obj = {
clicks: 0,
incClicks: function() {
this.clicks += 1;
},
init: function() {
var that = this;
$("#element").click(function() {
that.incClicks();
});
}
};
65. Intermedio: this y arguments
•
bind al rescate
var obj = {
clicks: 0,
incClicks: function() {
this.clicks += 1;
},
init: function() {
$("#element").click(
bind(this, this.incClicks)
);
}
};
66. intermedio: this y arguments
¿Qué hace esta otra función?
function enigma(fn) {
var slice = Array.prototype.slice,
args = slice.call(arguments, 1);
return function() {
var newargs = slice.call(arguments);
return fn.apply(this, args.concat(newargs));
};
}
67. intermedio: this y arguments
function enigma(fn) {
var slice = Array.prototype.slice,
args = slice.call(arguments, 1);
return function() {
var newargs = slice.call(arguments);
return fn.apply(this, args.concat(newargs));
};
}
typeof enigma(); // ???
68. intermedio: this y arguments
function enigma(fn) {
var slice = Array.prototype.slice,
args = slice.call(arguments, 1);
return function() {
var newargs = slice.call(arguments);
return fn.apply(this, args.concat(newargs));
};
}
var cosa = enigma();
typeof cosa(); // ???
69. intermedio: this y arguments
function enigma(fn) {
var slice = Array.prototype.slice,
args = slice.call(arguments, 1);
return function() {
var newargs = slice.call(arguments);
return fn.apply(this, args.concat(newargs));
};
}
var cosa = enigma(function() {
return "Hola!";
});
cosa(); // ???
70. intermedio: this y arguments
function enigma(fn) {
var slice = Array.prototype.slice,
args = slice.call(arguments, 1);
return function() {
var newargs = slice.call(arguments);
return fn.apply(this, args.concat(newargs));
};
}
function saluda(nombre) {
return "Hola, " + nombre + "!";
}
var cosa = enigma(saluda);
cosa("Mundo"); // ???
71. intermedio: this y arguments
function enigma(fn) {
var slice = Array.prototype.slice,
args = slice.call(arguments, 1);
return function() {
var newargs = slice.call(arguments);
return fn.apply(this, args.concat(newargs));
};
}
function saluda(nombre) {
return "Hola, " + nombre + "!";
}
var cosa = enigma(saluda, "Mundo");
cosa(); // ???
72. intermedio: this y arguments
function enigma(fn) {
var slice = Array.prototype.slice,
args = slice.call(arguments, 1);
return function() {
var newargs = slice.call(arguments);
return fn.apply(this, args.concat(newargs));
};
}
function saluda(saludo, nombre) {
return saludo + ", " + nombre + "!";
}
var cosa = enigma(saluda, "Hola", "Mundo");
cosa(); // ???
73. intermedio: this y arguments
function enigma(fn) {
var slice = Array.prototype.slice,
args = slice.call(arguments, 1);
return function() {
var newargs = slice.call(arguments);
return fn.apply(this, args.concat(newargs));
};
}
function saluda(saludo, nombre) {
return saludo + ", " + nombre + "!";
}
var cosa = enigma(saluda, "Hola");
cosa("Mundo"); // ???
cosa("Don Pepito"); // ???
74. intermedio: this y arguments
function enigma(fn) {
var slice = Array.prototype.slice,
args = slice.call(arguments, 1);
return function() {
var newargs = slice.call(arguments);
return fn.apply(this, args.concat(newargs));
};
}
var dario = {nombre: "Darío"};
var elena = {nombre: "Elena"};
function saluda(saludo) {
return saludo + ", " + this.nombre + "!";
}
var cosa = enigma(saluda, "Qué pasa");
cosa.call(dario); // ???
cosa.call(elena); // ???
75. Intermedio: this y arguments
•
curry: aplicación parcial de una función
function curry(fn) {
var slice = Array.prototype.slice,
args = slice.call(arguments, 1);
return function() {
var newargs = slice.call(arguments);
return fn.apply(this, args.concat(newargs));
};
}
76. Intermedio: rizar el rizo
var unObj = {
nombre: "Manuel",
edad: 32
};
function
function
function
function
getNombre() { return this.nombre; }
setNombre(nombre) { this.nombre = nombre; }
getEdad() { return this.edad; }
setEdad(edad) { this.edad = edad; }
var bindToUnObj = curry(bind, unObj),
getUnObjNombre = bindToUnObj(getNombre),
setUnObjNombre = bindToUnObj(setNombre);
setUnObjNombre("Pepito");
getUnObjNombre(); // ???
77. Intermedio: rizar el rizo
function getter(prop) { return this[prop]; }
function setter(prop, value) { this[prop] = value; }
var manuel = {
nombre: "Manuel",
edad: 32
};
var edadDeManuel = bind(manuel, curry(getter, "edad"));
edadDeManuel(); // ???
82. Prototipos
obj
var obj = {uno: 1, dos: 2};
uno
1
dos
2
Si hacemos:
obj.tres; // undefined
obj
uno
1
dos
2
Not found!
undefined
83. Prototipos
obj
var obj = {uno: 1, dos: 2};
uno
1
dos
2
¿De dónde sale?
obj.toString(); //
'[object Object]'
obj
uno
1
dos
2
Not found!
undefined
¿?
90. Prototipos
Pero... ¿Cómo establezco el prototipo de un objeto?
- No se puede hacer directamente
- No se puede modificar el prototipo de objetos literales
- Solo objetos generados (con new)
- Constructores!
91. Repaso: Mensajes
• Una función se puede ejecutar de 4 maneras:
- Invocando directamente la función
(function() { alert("Hey!"); })();
- Enviando un mensaje a un objeto (método)
objeto.metodo();
- Como constructor
new MiConstructor();
- Indirectamente, a través de call(...)
fn.call({}, "param");
y
apply(...)
92. Repaso: Mensajes
Una función se puede ejecutar de 4 maneras:
Invocando directamente la función
Enviando un mensaje a un objeto (método)
- Como constructor
new MiConstructor();
93. Constructores
• Funciones
• Invocación precedida por new
• Su contexto es un objeto recién generado
• return implícito
• La única manera de manipular prototipos
94. Constructores
function Constructor(param) {
// this tiene otro significado!
this.propiedad = "una propiedad!";
this.cena = param;
}
var instancia = new Constructor("Pollo asado");
instancia.propiedad; // una propiedad!
instancia.cena; // "Pollo asado"
95. Constructores
function Constructor(param) {
// this tiene otro significado!
this.propiedad = "una propiedad!";
this.cena = param;
}
var instancia = new Constructor("Pollo asado");
instancia.propiedad; // una propiedad!
instancia.cena; // "Pollo asado"
96. Constructores
3 pasos:
1. Crear un nuevo objeto
2. Prototipo del objeto = propiedadad prototype del
constructor
3. El nuevo objeto es el contexto del constructor
97. Constructores
var b = {
uno: 1,
dos: 2
};
function A() {
this.tres = 3;
this.cuatro = 4;
}
A.prototype = b;
var instancia = new A();
instancia.tres; // 3
instancia.uno; // 1
98. Constructores
var b = {
uno: 1,
dos: 2
};
instancia
A.prototype = b;
var instancia = new A();
instancia.tres; // 3
instancia.uno; // 1
1
dos
2
proto
function A() {
this.tres = 3;
this.cuatro = 4;
}
uno
b
tres
3
cuatro
4
proto
Object
b
99. Constructores
.hasOwnProperty(name)
• Distinguir las propiedades heredadas de las propias
• true solo si la propiedad es del objeto
instancia.hasOwnProperty("tres"); // true
instancia.hasOwnProperty("uno"); // false
100. Constructores
¿Qué pasa aquí?
var comun = { empresa: "ACME" };
function Empleado(nombre) {
this.nombre = nombre;
}
Empleado.prototype = comun;
var pepe = new Empleado("Pepe");
pepe.nombre; // "Pepe"
pepe.empresa; // ???
101. Constructores
¿Qué pasa aquí?
var comun = { empresa: "ACME" };
function Empleado(nombre) {
this.nombre = nombre;
}
Empleado.prototype = comun;
var pepe = new Empleado("Pepe");
comun.empresa = "Googlezon";
var antonio = new Empleado("Antonio");
antonio.empresa; // ???
102. Constructores
¿Qué pasa aquí?
var comun = { empresa: "ACME" };
function Empleado(nombre) {
this.nombre = nombre;
}
Empleado.prototype = comun;
var pepe = new Empleado("Pepe");
comun.empresa = "Googlezon";
var antonio = new Empleado("Antonio");
pepe.empresa; // ???
107. Prototipos
Es decir:
• Las propiedades de los prototipos se comparten!
• Se resuelven dinámicamente
• Modificar un prototipo afecta a todas las instancias
anteriores (y futuras)!
109. Intermedio: constructores
¿Cómo hacer que C herede de B que hereda de A?
C
A
B
uno
1
dos
2
tres
3
proto
B
proto
A
proto
Object
var instancia = new C();
instancia.tres; // 3
111. Intermedio: constructores
var B = {dos: 2};
function C() {
this.uno = 1;
}
C.prototype = B;
var instancia = new C();
instancia.tres;
112. Intermedio: constructores
var A = {tres: 3};
function B() {
this.dos = 2;
}
B.prototype = A;
function C() {
this.uno = 1;
}
C.prototype = B;
var instancia = new C();
instancia.tres;
113. Intermedio: constructores
var A = {tres: 3};
function B() {
this.dos = 2;
}
B.prototype = A;
function C() {
this.uno = 1;
}
C.prototype = B;
var instancia = new C();
instancia.dos; // !!!
114. Intermedio: constructores
var A = {tres: 3};
function B() {
this.dos = 2;
}
B.prototype = A;
function C() {
this.uno = 1;
}
C.prototype = B;
typeof C.prototype; // ???
C.prototype.dos;
// ???
115. Intermedio: constructores
var A = {tres: 3};
function B() {
this.dos = 2;
}
B.prototype = A;
function C() {
this.uno = 1;
}
C.prototype = new B();
var instancia = new C();
instancia.tres;
116. Intermedio: constructores
function A() {
this.tres = 3;
}
function B() {
this.dos = 2;
}
B.prototype = new A();
function C() {
this.uno = 1;
}
C.prototype = new B();
var instancia = new C();
instancia.tres;
117. Cadena de prototipos
La herencia en varios niveles necesita:
• Encadenar prototipos
• El prototipo del “sub constructor” ha de ser siempre
new Padre()
• Es la única manera de mantener el “padre del padre” en
la cadena!
138. Herencia clásica: Cuidado!
Este esquema tiene varios problemas:
• ¡No es fácil invocar al super constructor!
• No es fácil encapsular
139. Herencia clásica: Cuidado!
function MiClase() {
this.propPublica = "pública!";
}
MiClase.prototype.metPublico = function() {
return "público!";
}
var instancia = new MiClase();
instancia.propPublica; // "pública!"
instancia.metPublico();
140. Herencia clásica: Cuidado!
Este esquema tiene varios problemas:
• ¡No es fácil invocar al super constructor!
• No es fácil encapsular...
• ¡Se crea una instancia solo para mantener la cadena de
prototipos!
141. Herencia clásica: Cuidado!
function Superclase() {
operacionMuyCostosa();
alert(“Oh, no!”);
}
function MiClase() {
// ...
}
MiClase.prototype = new Superclase();
145. El “simple”
“Hagamos una funcioncita!”
function inherits(subClass, superClass) {
var F = function() {};
F.prototype = superClass.prototype;
subClass.prototype = new F();
subClass.prototype.constructor = subClass;
// extra
subClass.prototype.superclass = superClass;
}
146. El “simple”
function Animal() { }
Animal.prototype.mover = function() {
console.log("El animal se mueve...");
};
Animal.prototype.comer = function() {
console.log("¡Ñam!");
};
function Perro(raza) {
this.superclass.call(this);
}
inherits(Perro, Animal);
Perro.prototype.comer = function() {
console.log("El perro va a por su plato...");
this.superclass.prototype.comer.call(this);
};
var p = new Perro("terrier");
p.mover();
p.comer();
p instanceof Perro;
147. El “simple”
function Animal() { }
Animal.prototype.mover = function() {
console.log("El animal se mueve...");
};
Animal.prototype.comer = function() {
console.log("¡Ñam!");
};
function Perro(raza) {
this.superclass.call(this);
}
inherits(Perro, Animal);
Perro.prototype.comer = function() {
console.log("El perro va a por su plato...");
this.superclass.prototype.comer.call(this);
};
var p = new Perro("terrier");
p.mover();
p.comer();
p instanceof Perro;
OMG!
148. El “simple”
• Ventajas
- Muy simple de implementar
- Muy ligero
- No añade demasiado ruido
• Inconvenientes
- No soluciona mucho...
- No se “heredan” los métodos/propiedades de clase
- Sigue sin ser cómodo de usar
149. El “simple”
Caso práctico: CoffeeScript
var __hasProp = {}.hasOwnProperty,
__extends = function (child, parent) {
for (var key in parent) {
if (__hasProp.call(parent, key))
child[key] = parent[key];
}
function ctor() {
this.constructor = child;
}
ctor.prototype = parent.prototype;
child.prototype = new ctor();
child.__super__ = parent.prototype;
return child;
};
150. El “simple”
Caso práctico: CoffeeScript
var __hasProp = {}.hasOwnProperty,
__extends = function (child, parent) {
for (var key in parent) {
if (__hasProp.call(parent, key))
child[key] = parent[key];
}
function ctor() {
this.constructor = child;
}
ctor.prototype = parent.prototype;
child.prototype = new ctor();
child.__super__ = parent.prototype;
return child;
};
151. El “simple”
var MiClase = (function(_super) {
__extends(MiClase, _super);
function MiClase() {
MiClase.__super__.constructor.apply(this, arguments);
this.miPropiedad = 1;
}
MiClase.prototype.miMetodo = function() {
return MiClase.__super__.miMetodo.call(this, "hola");
};
return MiClase;
})(Superclase);
153. El cómodo
Más complejo, pero merece la pena
var Persona = Class.extend({
init: function(nombre) {
console.log("Bienvenido,", nombre);
}
});
var Ninja = Persona.extend({
init: function(){
this._super("ninja");
}
esgrimirEspada: function(){
console.log("En guardia!");
}
});
154. El cómodo
Más complejo, pero merece la pena
var Persona = Class.extend({
init: function(nombre) {
console.log("Bienvenido,", nombre);
}
});
var Ninja = Persona.extend({
init: function(){
this._super("ninja");
}
esgrimirEspada: function(){
console.log("En guardia!");
}
});
155. Intermedio: klass.js
¡Rellena los huecos!
var Class = function(){};
Class.extend = function(prop) {
var _super = this.prototype;
// ...
return Klass;
};
156. Intermedio: klass.js
Está muy bien pero...
• No hay métodos de clase (y no se heredan!)
• Todo sigue siendo público
• ¡Es solo una primera versión!
157. El cómodo
¿Cuándo usar este método?
• ¡Siempre que sea posible!
Contras:
• La implementación es más compleja...
• Hay que incluir la librería externa
• No es el enfoque “tradicional”
161. Herencia de prototipos
Herencia clásica: categorías
-Definir “Persona”
-crear instancias de persona según la definición
-Para hacer más concreto, ¡redefine!
162. Herencia de prototipos
Herencia clásica: categorías
-Definir “Persona”
-crear instancias de persona según la definición
-Para hacer más concreto, ¡redefine!
Herencia de prototipos: ejemplos
-“Como ese de ahí, pero más alto”
-Cualquier objeto concreto puede servir de ejemplo
-Para hacer más concreto, ¡cambia lo que quieras!
166. Herencia de prototipos
También se puede generalizar
var Animal = {
vivo: true,
comer: function() {
console.log("Ñam, ñam");
}
};
var Perro = clone(Animal);
Perro.especie = "perro";
var Dogo = clone(Perro);
Dogo.raza = "dogo";
var toby = clone(Dogo);
toby.nombre = "Toby";
167. Herencia de prototipos
¡Así de simple!
function clone(obj) {
function F(){}
F.prototype = obj;
return new F();
}
168. Herencia de prototipos
Herencia clásica vs. de prototipos
• Clásica
✓ MUCHO más extendida y bien comprendida
✓ Mayor catálogo de mecanismos de abstracción
• Prototipos
✓ Uso mucho más eficiente de la memoria
✓ La “auténtica” herencia en JS
✓ Muy simple
169. Herencia de prototipos
Peeero...
• Clásica
๏ Solo se puede emular. Necesario entender los prototipos.
๏ A contrapelo
• Prototipos
๏ Bastante limitada
๏ Lenta con cadenas de prototipos largas!
181. Intermedio: prototipos
Es decir:
• Hay asimetría entre escritura y lectura!
• Lectura: busca en la cadena
• Escritura: crea una nueva propiedad
• Es el comportamiento natural
• Uso eficiente de la memoria: solo se crean los valores
diferentes.
182. Intermedio: prototipos
¿Qué sucede?
var Lista = {
elementos: []
};
var laCompra = clone(Lista);
laCompra.elementos.push("Leche");
laCompra.elementos.push("Huevos");
var toDo = clone(Lista);
toDo.elementos.push("Contestar emails");
toDo.elementos.push("Subir a producción");
toDo.elementos;
183. prototipos
Object.create(proto)
• Igual que clone
• Nativo en algunos navegadores
var pluma = {
peso: 1,
enStock: true,
};
var plomo = Object.create(pluma);
plomo.peso = 100;
200. Clausuras
•
bind: fija una función a un contexto
function bind(ctx, fn) {
return function() {
return fn.apply(ctx, arguments);
}
}
201. Clausuras
•
curry: aplicación parcial de una función
function curry(fn) {
var slice = Array.prototype.slice,
args = slice.call(arguments, 1);
return function() {
var newargs = slice.call(arguments);
return fn.apply(this, args.concat(newargs));
};
}
202. Clausuras
Es decir:
• Las clausuras son función + entorno
• Asocian datos a funciones
• No se puede acceder directamente a las variables
clausuradas desde el exterior de la función
• Duración indefinida
• ¡Son muy útiles!
203. Intermedio: herencia funcional
function PseudoConstructor() {
var self = {};
self.propiedad = "valor";
return self;
}
var pseudoInstancia = PseudoConstructor();
pseudoInstancia.propiedad; // "valor"
204. Intermedio: herencia funcional
function PseudoConstructor() {
var self = {};
self.propiedad = "valor";
self.metodo = function(nombre) {
return "No te duermas, " + nombre + "!";
};
return self;
}
var pseudoInstancia = PseudoConstructor();
pseudoInstancia.metodo("Abraham");
205. Intermedio: herencia funcional
function PseudoConstructor() {
var self = {};
self.propiedad = "valor";
self.metodo = function(nombre) {
return "No te duermas, " + nombre + "!";
};
return self;
}
var pseudoInstancia = PseudoConstructor();
pseudoInstancia.metodo("Abraham");
206. Intermedio: herencia funcional
function PseudoConstructor() {
var self = {},
propiedad = "privada!";
var metodoPrivado = function(s) {
return s.toUpperCase();
}
self.metodo = function(nombre) {
var mayus = metodoPrivado(nombre);
return "No te duermas, " + mayus + "!";
};
return self;
}
var pseudoInstancia = PseudoConstructor();
pseudoInstancia.metodo("Abraham");
207. Intermedio: herencia funcional
1. Crear un pseudoconstructor
2. Definir una variable self con un objeto vacío
3. Añadir las propiedades/métodos públicos a self
4. Devolver self
211. Intermedio: herencia funcional
function A() {
var self = {};
self.metodo = function() {
console.log("A");
}
return self;
}
function B() {
var self = A();
var superMetodo = self.metodo;
self.metodo = function() {
superMetodo();
console.log("B");
}
return self;
}
212. Intermedio: herencia funcional
“Herencia funcional”:
✓ Explotar clausuras y objetos en linea
✓ Extremadamente simple e intuitivo
✓ Mejor encapsulado público/privado
✓ Poco ruido sintáctico
✓ No hacen falta helpers ni librerías
213. Intermedio: herencia funcional
“Herencia funcional”:
✓ Explotar clausuras y objetos en linea
✓ Extremadamente simple e intuitivo
✓ Mejor encapsulado público/privado
✓ Poco ruido sintáctico
✓ No hacen falta helpers ni librerías
๏ Un poco... ¿cutre?
๏ No es la manera más popular
๏ ¡Peor uso de la memoria!
215. ¿Programación funcional?
La vamos a entender como
• Creación y manipulación de funciones
• Alteración de funciones
• Aplicación de funciones
• Asincronía
216. Funciones de orden superior
Funciones que devuelven funciones
• curry
• bind
• ¡Muchas otras!
217. Funciones de orden superior
Algunas de las más útiles:
• throttle
• debounce
• once
• after
• compose
• memoize
218. throttle
Controlar la frecuencia de invocación
• La función se invocará como máximo una vez
• Durante el periodo de tiempo especificado
220. throttle
function throttle(fn, time) {
var last = 0;
return function() {
var now = new Date();
if ((now - last) > time) {
last = now;
return fn.apply(this, arguments);
}
}
}
221. debounce
Ejecutar la función cuando se deje de llamar
• La llamada se pospone hasta que pasen x ms
• Desde la última invocación
223. debounce
function debounce(fn, time) {
var timerId;
return function() {
var args = arguments;
if (timerId) clearTimeout(timerId);
timerId = setTimeout(bind(this, function() {
fn.apply(this, args);
}), time);
}
}
224. once
La función solo se puede invocar una vez
var counter = 0,
inc = function() {
counter++;
};
inc = once(inc);
for (var i=100000; i--;) {
inc();
}
alert(counter);
225. once
function once(fn) {
var executed = false;
return function() {
if (!executed) {
executed = true;
return fn.apply(this, arguments);
}
}
}
226. after
La función se ejecuta solo tras haber sido invocada n
veces
var counter = 0,
inc = function() {
counter++;
};
inc = after(inc, 1000);
for (var i=100000; i--;) {
inc();
}
alert(counter);
227. after
function after(fn, n) {
var times = 0;
return function() {
times++;
if (times % n == 0) {
return fn.apply(this, arguments);
}
}
}
228. compose
Composición de funciones
function multiplier(x) {
return function(y) { return x*y; }
}
var randCien = compose(Math.floor,
multiplier(100),
Math.random);
alert(randCien());
229. compose
function compose() {
var fns = [].slice.call(arguments);
return function(x) {
var currentResult = x, fn;
for (var i=fns.length; i--;) {
fn = fns[i];
currentResult = fn(currentResult);
}
return currentResult;
}
}
230. memoize
Nunca calcules el mismo resultado 2 veces!
• La primera invocación calcula el resultado
• Las siguientes devuelven el resultado almacenado
• Solo vale para funciones puras
231. memoize
function fact(x) {
if (x == 1) { return 1; }
else { return x * fact(x-1); }
}
fact = memoize(fact);
var start = new Date();
fact(100);
console.log(new Date() - start);
start = new Date();
fact(100);
console.log(new Date() - start);
232. memoize
function memoize(fn) {
var cache = {};
return function(p) {
var key = JSON.stringify(p);
if (!(key in cache)) {
cache[key] = fn.apply(this, arguments);
}
return cache[key];
}
}
235. Asincronía
¿Cómo devuelvo el valor random desde dentro?
function asincrona() {
var random = Math.floor(Math.random() * 100);
setTimeout(function() {
return random;
}, random);
}
236. Asincronía
function asincrona(callback) {
var random = Math.floor(Math.random() * 1000);
setTimeout(function() {
callback(random);
}, random);
}
asincrona(function(valor) {
alert(valor);
});
237. Asincronía
function asincrona(callback) {
var random = Math.floor(Math.random() * 1000);
setTimeout(function() {
callback(random);
}, random);
}
asincrona(function(valor) {
alert(valor);
});
238. Asincronía
function asincrona(callback) {
var random = Math.floor(Math.random() * 1000);
setTimeout(function() {
callback(random);
}, random);
}
asincrona(function(valor) {
alert(valor);
});
240. Asincronía
Promesas
• Una idea muy sencilla:
- Un objeto que representa un estado futuro
• El estado futuro puede ser:
- La resolución de la promesa en un valor
- El rechazo de la promesa con un error
• Mucho, mucho más fácil de manejar que los callbacks
241. Promesas
function onSuccess(data) {
/* ... */
}
function onFailure(e) {
/* ... */
}
var promesa = $.get('/mydata');
promesa.then(onSuccess, onFailure);
247. Promesas
Casos: cuando onSuccess devuelve un valor
/* siendo promise una promesa... */
promise.then(function() {
return 42;
}).then(function(valor) {
return "La respuesta es " + valor;
}).then(function(mensaje) {
console.log(mensaje);
});
248. Promesas
Casos: cuando onSuccess devuelve un valor
/* siendo promise una promesa... */
promise.then(function() {
return 42;
}).then(function(valor) {
return "La respuesta es " + valor;
}).then(function(mensaje) {
console.log(mensaje);
});
249. Promesas
Casos: llamando varias a veces a .then
/* siendo promise una promesa... */
promise.then(function() {
console.log("primer onSuccess!");
});
promise.then(function() {
console.log("segundo onSuccess!");
});
250. Promesas
Casos: llamando varias a veces a .then
/* siendo promise una promesa... */
promise.then(function() {
console.log("primer onSuccess!");
}, function(e) {
console.log("primer onFailure...");
});
promise.then(function() {
console.log("segundo onSuccess!");
}, function(e) {
console.log("segundo onFailure...");
});
251. Promesas
Casos: capturar errores
/* siendo promise una promesa... */
promise.then(function() {
throw new Error("Oops!");
}).then(function() {
console.log("Nunca llegamos aquí...");
}, function(e) {
console.log("Vaya por Dios!");
console.log(e);
});
252. Promesas
Casos: capturar errores
/* siendo promise una promesa... */
promise.then(function() {
throw new Error("Oops!");
}).then(function() {
console.log("Nunca llegamos aquí...");
}, function(e) {
console.log("Vaya por Dios!");
console.log(e);
});
253. Promesas
Casos: cascada de errores
/* siendo promise una promesa... */
promise.then(function() {
throw new Error("Oh no!");
}).then(function() {
console.log("Nunca se ejecuta.");
}).then(function() {
console.log("Esto tampoco.");
}, function(e) {
console.log("Vaya por Dios!");
console.log(e);
});
254. Promesas
Casos: cascada de errores
/* siendo promise una promesa... */
promise.then(function() {
throw new Error("Oh no!");
}).then(function() {
console.log("Nunca se ejecuta.");
}).then(function() {
console.log("Esto tampoco.");
}, function(e) {
console.log("Vaya por Dios!");
console.log(e);
});
255. Promesas
Casos: errores localizados
/* siendo promise una promesa... */
promise.then(function() {
throw new Error("Oh no!");
}).then(function() {
console.log("Nunca se ejecuta.");
}, function(e) {
console.log("Manejador del error");
}).then(function() {
/* ... */
}, function(e) {
/* este manejador no se ejecuta! */
});
256. Promesas
Casos: errores localizados
/* siendo promise una promesa... */
promise.then(function() {
throw new Error("Oh no!");
}).then(function() {
console.log("Nunca se ejecuta.");
}, function(e) {
console.log("Manejador del error");
}).then(function() {
/* ... */
}, function(e) {
/* este manejador no se ejecuta! */
});
258. Promesas
Deferreds o diferidos
• Objetos que nos permiten crear y controlar promesas
de valores futuros
• Dos operaciones:
- resolve: resuelve la promesa como exitosa
- reject: rechaza la promesa como fracasada
260. Promesas
function enDiezSegundos() {
var diferido = new R.Deferred();
setTimeout(function() {
diferido.resolve(new Date());
}, 10*1000);
return diferido.promise();
}
var promesa = enDiezSegundos();
promesa.then(function(elFuturo) {
console.log("Ya han pasado diez segundos!");
console.log(elFuturo.getTime());
});
261. Promesas
Deferred#resolve([arg1, arg2, ...])
• Resuelve la promesa (ejecuta el callback onSuccess)
• Los parámetros con los que se llame a .resolve()
serán los que reciba el callback onSuccess
• Solo se debería llamar una vez
262. Promesas
Deferred#reject([arg1, arg2, ...])
• Rechaza la promesa (ejecuta el callback onFailure)
• Los parámetros con los que se llame a .reject() serán
los que reciba el callback onFailure
• Solo se debería llamar una vez
265. Promesas
Vamos a crear una librería de promesas
• Una implementación sencilla
• Que satisfaga la especificación Promises/A+
- http://promises-aplus.github.com/promises-spec/
• tema2/r-promise/index.html
266. Promesas
Por dónde empezar:
• Poder crear instancias de diferidos
• Poder poner un callback de éxito y uno de fracaso
• .then()
- Por ahora, que no devuelva nada
- Solo se puede llamar a una vez por diferido
• .resolve([arg1,
...])
y .reject([arg1,
- Invocan el callback adecuado
- Pasándole los parámetros adecuados
...])
267. Promesas
Siguientes pasos:
• Poder invocar a .then() varias veces
- Es decir, tener varios callbacks para cada caso en un mismo
diferido
• Que funcione el primer ejemplo del ejercicio
Lo último a abordar:
• Que las llamadas a .then() se puedan encadenar
• Es decir, que .then() devuelva a su vez una promesa
• Que funcione el segundo ejemplo
268. Promesas
when(pov1 [, pov2, ...])
• Dos utilidades:
- Homogeneizar promesas y valores en el código
- Combinar varias promesas/valores
• Devuelve siempre una promesa
• La promesa devuelta:
- Se resolverá si todas las promesas se resuelven.
- Los parámetros del callback son los valores devueltos por
cada una de las promesas.
- Se rechazará en caso contrario
276. Principios de diseño
• SRP: Single Responsibility Principle
- El código de una elemento ha de tener solo una razón
para cambiar.
- EL principio de diseño
- También el complementario: cada responsabilidad ha de
tener un único lugar en el código (D.R.Y.)
277. SRP
Es común ver cosas como esta:
$.ajax({ ... })
.success(function() {
cambioEnInterfaz();
mostrarModal();
if ($("#elemento").value() == "Ok") {
/* ... */
}
globalSeHaGuardado = true;
})
.error(function() {
// ...
});
278. SRP
O como esta:
var Widget = Class.extend({
onClick: function() { ... },
guardar: function() { ... },
render: function() { ... },
mostrarError: function() { ... }
});
283. SRP
El resultado: caos!
• No hay un lugar claro para cada operación
• Es difícil entender qué hace cada línea
- El “qué” está enterrado en el “cómo”
• Muy complicado de testear
• Difícil de reutilizar
286. SRP
Caso práctico: BrowserQuest
• Todo el proyecto está muy bien estructurado
- entity.js
- character.js
- animation.js
- ...
• A pesar de ser muy grande, cada responsabilidad tiene
su sitio
287. SRP
Caso práctico: Backbone.js
• https://github.com/documentcloud/backbone/blob/
master/backbone.js
• en Backbone.Model, línea 179...
288. SRP
• Por un lado..
๏ gestión de estado (set, get)
๏ validación
๏ formateo (toJSON, escape)
๏ servidor (fetch, save)
• Por otro...
✓ Delega los detalles a otros módulos (Sync, Event)
✓ Bajo acoplamiento (“interfaces”)
289. SRP
“Una responsabilidad”...
• Subjetivo
• “Una sola razón para cambiar”...
- “Para qué todo funcione bien”
- Muy dependiente del nivel de abstracción
- Y de cada módulo
• El exceso es tan malo como el defecto
290. Principios de diseño
• Tell, Don’t Ask
- “Dime lo que necesitas”
- Claridad y expresividad
- Encapsular las comprobaciones
294. Tell, Don’t Ask
El error:
1. Preguntar a un objeto sobre su estado
2. Tomar una decisión
3. Decirle lo que tiene que hacer
295. Tell, Don’t Ask
El error:
1. Preguntar a un objeto sobre su estado
2. Tomar una decisión
3. Decirle lo que tiene que hacer
¡Probablemente ese código pertenece al objeto!
297. Tell, Don’t Ask
var Usuario = Class.extend({
saludar: function() {
if (this.primerLogin) {
this.mostrarMensajeBienvenida();
} else {
this.mostrarSaludo();
}
}
});
// y después...
usuario.saludar();
298. Tell, Don’t Ask
function comprobarTimeout(respuesta) {
if ((Date.now() - respuesta.start) > 10000) {
respuesta.notificarTimeout();
}
}
299. Tell, Don’t Ask
var Respuesta = Class.extend({
comprobarTimeout: function() {
if ((Date.now() - this.start) > 10000) {
this.notificarTimeout();
}
}
});
// y después...
respuesta.comprobarTimeout();
300. Tell, Don’t Ask
var elementos = miColeccion.getItems();
for (var i=0; i<elementos.length; i++) {
var elemento = elementos[i];
console.log(elemento.nombre);
}
302. Tell, Don’t Ask
var elemento = new Elemento("hola", 12);
var lista = miColeccion.getItems();
lista.addElementAt(elemento.getOrder(), elemento);
303. Tell, Don’t Ask
var elemento = new Elemento("hola", 12);
miColeccion.add(elemento);
304. Tell, Don’t Ask
Es decir:
• Los datos y las operaciones sobre esos datos deben
estar en el mismo sitio (objeto)
• Encapsular, desacoplar
• “Command/Query Separation”
- Consulta información
- Da una orden y deja al objeto decidir
- Pero no las mezcles!
305. Tell, Don’t Ask
Ventajas:
✓ Más robusto (menor acoplamiento)
✓ Menor tendencia a repetir lógica
✓ Mejor estructurado
Inconvenientes:
๏ Miles de métodos de 2 o 3 líneas
๏ “Ruido” en las clases
306. Principios de diseño
• S.O.L.I.D.
- Single Responsibility
- Open-Closed
- Liskov Substitution
- Interface Segregation
- Dependency Inversion
307. S.O.L.I.D.
Open-Closed
• “Un elemento ha de estar abierto a la extensión pero
cerrado a la modificación”
- Abierto a la extensión: poder ser adaptado a las (futuras)
necesidades de la aplicación
- Cerrado a la modificación: que la adaptación no implique
modificar su código
308. Open-Closed
var Lenguas = { Castellano: 0,
Ingles: 1 };
var Persona = Class.extend({
init: function(lengua) { this.lengua = lengua; },
saludar: function(lengua) {
if (this.lengua == Lenguas.Castellano) {
alert("Hola!");
} else if (this.lengua == Lenguas.Ingles) {
alert("Hello!");
}
}
});
new Persona(Lenguas.Castellano).saludar();
309. Open-Closed
var Persona = Class.extend({
saludar: function() {
alert(this.saludo);
}
});
var Angloparlante = Persona.extend({
init: function() { this.saludo = "Hello!"; }
});
var Hispanohablante = Persona.extend({
init: function() { this.saludo = "Hola!"; }
});
new Hispanohablante().saludar();
310. Open-Closed
Pretende:
• Promover el uso de abstracciones
• Código modular y flexible ante el cambio
• Evitar un torrente de cambios en cascada!
311. Open-Closed
Pretende:
• Promover el uso de abstracciones
• Código modular y flexible ante el cambio
• Evitar un torrente de cambios en cascada!
Es decir:
• Especificar y respetar interfaces
312. Open-Closed
var Canvas = Class.extend({
render: function(figura) {
if (figura instanceof Triangulo) {
// ...
} else if (figura instanceof Cuadrado) {
// ...
}
}
});
313. Open-Closed
var Canvas = Class.extend({
render: function(figura) {
figura.draw(this);
}
});
var Triangulo = Figura.extend({
draw: function(canvas) { ...}
});
var Cuadrado = Figura.extend({
draw: function(canvas) { ...}
});
318. S.O.L.I.D.
Sustitución de Liskov
• “Un objeto debe ser substituible por instancias de sus
subclases”
- Si B es subclase de A
- Y b es una instancia de B
- Se debería poder usar b allí donde se espere un objeto de
clase A
320. Sustitución de Liskov
var Animal = Class.extend({
caminar: function() { /*...*/ },
comer: function() { /* ... */ }
});
var Serpiente = Animal.extend({
/* ... */
});
var s = new Serpiente();
s.caminar();
321. Sustitución de Liskov
Pretende:
• Promover la reutilización segura de código
• Mantener una semántica coherente
• Si “B es un A”, entonces “B ha de comportarse como A”
322. S.O.L.I.D.
Segregación de la Intefaz
• “muchas interfaces cliente específicas son mejores que
una interfaz de propósito general”
- No obligues a un cliente a depender de interfaces que no
necesita
- Polución de interfaz
323. Segregación de la intefaz
var Modal = Class.extend({
show: function() { ... },
hide: function() { ... }
});
324. Segregación de la intefaz
var Modal = Class.extend({
show: function() { ... },
hide: function() { ... }
});
var Timer = Class.extend({
setTimer: function(time) { ... },
startTimer: function() { ... },
onTimeout: function() { ... }
});
327. Segregación de la intefaz
var WidgetView = Class.extend({
init: function(cont) {
var cbind = curry(bind, cont);
$("#E1").click(cbind(cont.onE1Click));
}
});
328. Segregación de la intefaz
var WidgetView = Class.extend({
init: function(cont) {
var cbind = curry(bind, cont);
$("#E1").click(cbind(cont.onE1Click));
$("#E2").click(cbind(cont.onE2Click));
$("#E3").click(cbind(cont.onE3Click));
$("#E4").click(cbind(cont.onE4Click));
}
});
329. Segregación de la intefaz
var WidgetView = Class.extend({
init: function(cont) {
var cbind = curry(bind, cont);
$("#E1").click(cbind(cont.onE1Click));
$("#E2").click(cbind(cont.onE2Click));
$("#E3").click(cbind(cont.onE3Click));
$("#E4").click(cbind(cont.onE4Click));
$("#save").click(cbind(cont.save));
$("#reset").click(cbind(cont.reset));
$("#validate").click(cbind(cont.validate));
$("#next-page").click(cbind(cont.getNextPage));
// ...
}
});
330. Segregación de la intefaz
var WidgetView = Class.extend({
init: function(viewCont, cont, pagCont) {
var vbind = curry(bind, cont),
cbind = curry(bind, cont),
pbind = curry(bind, pagCont);
$("#E1").click(vbind(viewCont.onE1Click));
$("#E2").click(vbind(viewCont.onE2Click));
$("#E3").click(vbind(viewCont.onE3Click));
$("#E4").click(vbind(viewCont.onE4Click));
$("#save").click(cbind(cont.save));
$("#reset").click(cbind(cont.reset));
$("#validate").click(cbind(cont.validate));
$("#next-page").click(pbind(pagCont.getNextPage));
// ...
}
});
331. Segregación de la intefaz
var WidgetView = Class.extend({
init: function(viewCont, cont, pagCont) {
var vbind = curry(bind, cont),
cbind = curry(bind, cont),
pbind = curry(bind, pagCont);
$("#E1").click(vbind(viewCont.onE1Click));
$("#E2").click(vbind(viewCont.onE2Click));
$("#E3").click(vbind(viewCont.onE3Click));
$("#E4").click(vbind(viewCont.onE4Click));
$("#save").click(cbind(cont.save));
$("#reset").click(cbind(cont.reset));
$("#validate").click(cbind(cont.validate));
$("#next-page").click(pbind(pagCont.getNextPage));
// ...
}
});
332. S.O.L.I.D.
Dependency inversion
• “Depende de abstracciones. No dependas de
cocreciones”
- Entidades de alto nivel no deben depender de entidades de
bajo nivel. Ambos deben depender de abstracciones.
- Las abstracciones no deben depender de detalles. Los detalles
deben depender de abstracciones.
333. Dependency Inversion
var Model = Class.extend({
save: function() {
var tbind = curry(bind, this),
stop = bind(this.icon, this.icon.stop);
$.post(this.url, this.getData())
.success(tbind(this.saved))
.error(tbind(this.saveFailed))
.complete(stop);
}
});
334. Dependency Inversion
var Model = Class.extend({
save: function() {
var tbind = curry(bind, this),
stop = bind(this.icon, this.icon.stop);
$.post(this.url, this.getData())
.success(tbind(this.saved))
.error(tbind(this.saveFailed))
.complete(stop);
}
});
335. Dependency Inversion
var Model = Class.extend({
init: function(store) { this.store = store; }
save: function() {
var tbind = curry(bind, this),
stop = bind(this.icon, this.icon.stop);
this.store.save(
this.data,
tbind(this.saved),
tbind(this.saveFailed),
stop
);
}
});
var Store = Class.extend({
save: function(data, success, error, complete) {
});
}
346. Intermedio: merge
¿Cómo sería esa función merge?
function merge() {
var slice = Array.prototype.slice,
sources = slice.call(arguments),
target = {};
sources.forEach(function(source) {
for (var p in source) if (source.hasOwnProperty(p)) {
target[p] = source[p];
}
});
return target;
}
347. Módulos y namespaces
• JavaScript no tiene concepto de namespace
• Todo tirado en objeto global
- Mucha polución
- Colisión de nombres
- Difícil de navegar
348. Módulos y namespaces
• JavaScript no tiene concepto de namespace
• Todo tirado en objeto global
- Mucha polución
- Colisión de nombres
- Difícil de navegar
•¡Pero tenemos funciones!
350. Módulos y namespaces
function sandbox() {
function miHelper() {
// ...
}
var miVariableTemporal = 0;
var estadoLocal = {};
}
351. Módulos y namespaces
(function sandbox() {
function miHelper() {
// ...
}
var miVariableTemporal = 0;
var estadoLocal = {};
}())
352. Módulos y namespaces
(function sandbox() {
function miHelper() {
// ...
}
var miVariableTemporal = 0;
var estadoLocal = {};
}())
353. Módulos y namespaces
function miFuncionUtil() {
// ...
}
function miGranMetodo() {
// ...
}
function miEstupendoHelper() {
// ...
}
354. Módulos y namespaces
function aux() { }
var state = "off";
function miFuncionUtil() {
// ...
}
function miGranMetodo() {
// ...
}
function miEstupendoHelper() {
// ...
}
355. Módulos y namespaces
(function() {
function aux() { }
var state = "off";
function miFuncionUtil() { }
function miGranMetodo() { }
function miEstupendoHelper() { }
}())
356. Módulos y namespaces
var Modulo = (function() {
function aux() { }
var state = "off";
function miFuncionUtil() { }
function miGranMetodo() { }
return {
miFuncionUtil: miFuncionUtil,
miGranMetodo: miGranMetodo
};
}());
357. Módulos y namespaces
var Modulo = (function() {
function aux() { }
var state = "off";
function miFuncionUtil() { }
function miGranMetodo() { }
return {
miFuncionUtil: miFuncionUtil,
miGranMetodo: miGranMetodo
};
}());
359. Módulos y namespaces
var Modulo = {};
(function(Modulo) {
function aux() { }
var state = "off";
Modulo.miFuncionUtil = function() { }
Modulo.miGranMetodo = function() { }
}(Modulo));
360. Módulos y namespaces
var Modulo = {};
(function(Modulo) {
function aux() { }
var state = "off";
Modulo.miFuncionUtil = function() { }
}(Modulo));
(function(Modulo) {
Modulo.miGranMetodo = function() { }
}(Modulo));
361. Módulos y namespaces
var Modulo = (function(Modulo) {
function aux() { }
var state = "off";
Modulo.miFuncionUtil = function() { }
return Modulo;
}(Modulo || {}));
var Modulo = (function(Modulo) {
Modulo.miGranMetodo = function() { }
return Modulo;
}(Modulo || {}));
362. Módulos y namespaces
var Modulo = (function(Modulo) {
function aux() { }
var state = "off";
Modulo.miFuncionUtil = function() { }
return Modulo;
}(Modulo || {}));
var Modulo = (function(Modulo) {
Modulo.miGranMetodo = function() { }
return Modulo;
}(Modulo || {}));
363. Módulos y namespaces
Una truco más sofisticado:
• Tenemos un módulo
• Al que añadimos propiedades en varios ficheros
• Queremos compartir cierta información entre ficheros
• Pero que no sea accesible una vez terminada la carga
372. Módulos y namespaces
Según crece la aplicación...
var MiLibreria = MiLibreria || {};
MiLibreria.widgets = MiLibreria.widgets || {};
373. Módulos y namespaces
Según crece la aplicación...
var MiLibreria = MiLibreria || {};
MiLibreria.widgets = MiLibreria.widgets || {};
MiLibreria.widgets.buttons = MiLibreria.widgets.buttons || {};
MiLibreria.widgets.buttons.actionButtons = (function(buttons) {
buttons.ok = new Widget({ ... });
buttons.cancel = new Widget({ ... });
}(MiLibreria.widgets.buttons.actionButtons || {}));
374. Módulos y namespaces
Namespaces, pero más cómodos:
MiLib.namespace('widgets.buttons.actionButtons', function(my) {
my.ok = new Widget({ ... });
my.cancel = new Widget({ ... });
});
376. Intermedio: namespace
¿Como sería la función MiLib.namespace?
var MiLib = (function(my) {
my.namespace = function(string, sandbox) {
// ???
};
return my;
}(MiLib || {}));
377. Intermedio: namespace
¿Como sería la función MiLib.namespace?
var MiLib = (function(my) {
my.namespace = function(string, sandbox) {
var spaces = string.split('.'),
root = my,
space;
while (space = spaces.shift()) {
root = root[space] || (root[space] = {});
}
return sandbox(root);
};
return my;
}(MiLib || {}));
378. Mixins
• Otra forma de reutilizar código
• Sin las limitaciones de la herencia
• Para código de propósito general
• Algo similar a herencia múltiple
385. Factoría
Delegar la creación de un objeto
• Elegir el constructor dinámicamente
• Procesos de construcción complejos
• Desacoplar detalles de implementación
386. Factoría
var locales = {
es: {header: {title: "Mi Título"}},
en: {header: {title: "My Title"}}
};
var I18n = Class.extend({
translate: function(path) {
var position = locales[this.locale],
path = path.split('.'),
currentPath;
while (currentPath = path.shift()) {
position = position[currentPath];
}
return position;
}
});
var english = new I18n();
english.locale = "en";
english.translate('header.title'); // “My
388. Factoría
var GlobalConfig = {locale: "es"};
I18n.withCurrentLocale = function() {
var instance = new this;
instance.locale = GlobalConfig.locale;
return instance;
};
389. Factoría
var i18n = I18n.withCurrentLocale();
alert(i18n.translate('header.title'));
390. Factoría
var Events = Class.extend({
on: function(event, cb) { },
off: function(event, cb) { }
});
var IEEvents = Events.extend({
on: function(event, cb) { },
off: function(event, cb) { }
});
Events.getInstance = function() {
if (checkForIExplorer()) {
return new IEEvents();
} else {
return new Events();
}
};
391. Factoría
Controlar las instancias
var Enemigo = (function() {
var enemigos = [];
var Enemigo = Class.extend({ /*...*/ });
Enemigo.crear = function() {
var instance;
if (enemigos.length < 5) {
instance = new Enemigo();
enemigos.push(instance);
return instance;
} else {
throw new Error("No puede haber más!");
}
}
return Enemigo;
}());
392. Factoría
var Recurso = (function() {
var libres = [];
var Recurso = Class.extend({
liberar: function() { libres.push(this); }
});
Recurso.crear = function() {
if (libres.length > 0) {
return libres.pop();
} else {
return new Recurso();
}
}
return Recurso;
}());
393. Factoría
¿Cuándo usar factorías?
• La construcción de un objeto es compleja
• Seleccionar el constructor adecuado según entorno
• Necesitamos controlar el instanciado
394. Singleton
Clase con una única instancia
• Un tipo peculiar de Factoría
• Cuando no tiene sentido más de una instancia
395. Intermedio: I18n.js
Librería de internacionalización
var Config = {locale: 'en'};
I18n.addTranslation('en', {header: {title: "My Title"}});
I18n.addTranslation('es', {header: {title: "Mi Título"}});
var i18n = I18n.withCurrentLocale();
alert(i18n.translate('header.title'));
// Singleton!
console.log(i18n === I18n.withCurrentLocale())