Curso Javascript profesionales

Post on 27-Jun-2015

1.039 views 4 download

Tags:

description

Documentación curso Javascript para profesionales por Redradix School. Curso avanzado de Javascript.

Transcript of Curso Javascript profesionales

Javascript para Profesionales

made with love by Redradix (www.redradix.com)

Fundamentos

Objetos

! Conjunto de propiedades propias + heredadas de otro objeto (prototipos)

! ¡Qué no cunda el pánico!

Objetos

! Dinámicos

! Set de strings

var obj = {};obj.nuevaPropiedad = 1;delete obj.nuevaPropiedad;

var strset = { hola: true, adios: true};"hola" in strset;

Objetos

! Referencias

! Todo son objetos excepto: strings, números, booleans, null o undefined

! Strings, números y booleans se comportan como objetos inmutables

var p1 = {x: 1}, p2 = p1;

p1 === p2; // truep1.x = 5;p2.x; // 5

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

Objetos

! Literales

- clase: Object

- sencillos y ligeros

! Construidos

- clase: prototype

- constructor

{ un: "objeto", literal: true};

new NombreDeUnaFuncion();

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.

Clases

Si esto es un “punto”

Y esto es otro “punto”

¿Qué es esto?

¿Y esto?

var point = {x: 0, y: 0};

var point2 = {x: 5, y: 5};

var what = {x: 10, y: 10};

var isit = {x: -10, y: -20};

Mensajes

• Teniendo:

• ¿Que significa esto?

var obj = { nombre: "Pepito", saludo: function () { return "Hola, Mundo!"; }};

obj.nombre;

Mensajes

• Teniendo:

• ¿Y esto?

var obj = { nombre: "Pepito", saludo: function () { return "Hola, Mundo!"; }};

obj.saludo;

Mensajes

• Teniendo:

• ¿Y esto otro? (¡cuidado!)

var obj = { nombre: "Pepito", saludo: function () { return "Hola, Mundo!"; }};

obj[“saludo”]();

Mensajes

• Teniendo:

• ¿Es lo mismo?

var obj = { nombre: "Pepito", saludo: function () { return "Hola, Mundo!"; }};

var fn = obj["saludo"];fn();

Mensajes

• Teniendo:

• ¡NO es no mismo!

var obj = { nombre: "Pepito", saludo: function () { return "Hola, Mundo!"; }};

var fn = obj["saludo"];fn();

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(...) y apply(...)

(function() { alert("Hey!"); })();

objeto.metodo();

new MiConstructor();

fn.call({}, "param");

Mensajes

- Invocando directamente la función

- Enviando un mensaje a un objeto (método)

(function() { alert("Hey!"); })();

objeto.metodo();

Mensajes

Un mensaje se envía a un receptor

var obj = {};obj.toString(); // [object Object]

"hola don Pepito".toUpperCase();

Mensajes

Un mensaje se envía a un receptor

var obj = {};obj.toString(); // [object Object]

"hola don Pepito".toUpperCase();

Mensajes

La sintaxis es engañosa

var obj = { coleccion: ["uno", "dos", "tres"], metodo: function() { return "Hola, Mundo!"; }};

obj.coleccion[1]; obj.metodo();vs.

Mensajes

obj.metodo();var fn = obj.metodo;fn();

Mensajes

obj.metodo();var fn = obj.metodo;fn();

- Accede a la propiedad “metodo” de obj

- Supongo que es una función y la invoco

Mensajes

obj.metodo();var fn = obj.metodo;fn();

- Accede a la propiedad “metodo” de obj

- Supongo que es una función y la invoco

- Envía el mensaje “metodo” a obj

- Si existe, obj se encarga de ejecutar la función

Mensajes

obj.metodo();var fn = obj.metodo;fn();

- Accede a la propiedad “metodo” de obj

- Supongo que es una función y la invoco

- NO HAY RECEPTOR

- Envía el mensaje “metodo” a obj

- Si existe, obj se encarga de ejecutar la función

- obj ES EL RECEPTOR

Mensajes

Un error típico:

$("#elemento").click(objeto.clickHandler);

Mensajes

Un error típico:

• Lo que se intenta decir:

- “Al hacer click sobre #elemento, envía el mensaje clickHandler a objeto”

$("#elemento").click(objeto.clickHandler);

Mensajes

Un error típico:

• 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”

$("#elemento").click(objeto.clickHandler);

El receptor: ...

¿Por qué tanto lío con el receptor del mensaje?

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

El receptor: this

this = “el receptor de este mensaje”

var nombre = "Sonia";

var obj = { nombre: "Pepito", saludo: function() { alert("hola " + this.nombre); }}

obj.saludo();

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

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

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

El receptor: this

Semánticamente, es como un parámetro oculto

que el receptor se encargara de proveer

function ([this]) { alert("hola " + this.nombre);}

obj.saludo(); => saludo([obj]);

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

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

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

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)

El receptor: this

Otro error común:

var obj = { clicks: 0, init: function() { $("#element").click(function() { this.clicks += 1; }); }};

obj.init();

El receptor: this

Otro error común:

var obj = { clicks: 0, init: function([this]) { $("#element").click(function([this]) { this.clicks += 1; }); }};

obj.init([obj]);

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

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(...) y apply(...)

(function() { alert("Hey!"); })();

objeto.metodo();

new MiConstructor();

fn.call({}, "param");

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(...) y apply(...)

fn.call({}, "param");

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étodosvar fn = function() { alert("Hey!"); };

fn.toString();

El receptor: this

• Dos métodos permiten manipular el receptor (contexto):

- fn.call(context [, arg1 [, arg2 [...]]])

- fn.apply(context, arglist)

var a = [1,2,3];Array.prototype.slice.call(a, 1, 2); // [2]

var a = [1,2,3];Array.prototype.slice.apply(a, [1, 2]); // [2]

El receptor: this

var nombre = "Objeto Global";

function saluda() { alert("Hola! Soy " + this.nombre);}

var alicia = { nombre: "Alicia"};

saluda();

saluda.call(alicia);

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

arguments

• Se comporta (más o menos) como Array...

• ...pero NO del todo

function echoArgs() { alert(arguments[0]); // 1}

echoArgs(1, 2, 3, 4);

function echoArgs() { return arguments.slice(0, 1); // Error!}

echoArgs(1, 2, 3, 4);

arguments

• Un truco:

function echoArgs() { var slice = Array.prototype.slice; return slice.call(arguments, 0, 1);}

echoArgs(1, 2, 3, 4); // [1]

arguments

¡Cuidado, se comporta como parámetro oculto!

function exterior() { var interior = function() { alert(arguments.length); }; interior();}

exterior("a", "b", "c");

intermedio: this y arguments

¿Qué hace esta función?

function misterio(ctx, fn) { return function() { return fn.apply(ctx, arguments); }}

Intermedio: this y arguments

function misterio(ctx, fn) { return function() { return fn.apply(ctx, arguments); }}

var algo = misterio();

typeof algo; // ???

Intermedio: this y arguments

function misterio(ctx, fn) { return function() { return fn.apply(ctx, arguments); }}

var algo = misterio();

algo(); // ???

Intermedio: this y arguments

function misterio(ctx, fn) { return function() { return fn.apply(ctx, arguments); }}

var algo = misterio({}, function() { return this;});

typeof algo(); // ???

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

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

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

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, "); /// ???

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, "); /// ???

Intermedio: this y arguments

• bind: fija una función a un contexto

function bind(ctx, fn) { return function() { return fn.apply(ctx, arguments); }}

Intermedio: this y arguments

Volviendo al problema:

var obj = { clicks: 0, init: function() { $("#element").click(function() {

// MAL this.clicks += 1; }); }};

obj.init();

Intermedio: this y arguments

Apaño:

var obj = { clicks: 0, init: function() { var that = this; $("#element").click(function() { that.clicks += 1; }); }};

obj.init();

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

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

Intermedio: this y arguments

• bind al rescate

var obj = { clicks: 0, incClicks: function() { this.clicks += 1; }, init: function() { $("#element").click( bind(this, this.incClicks) ); }};

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

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

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

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

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"); // ???

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

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

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"); // ???

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); // ???

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

Intermedio: rizar el rizo

var unObj = { nombre: "Manuel", edad: 32};

function getNombre() { return this.nombre; }function setNombre(nombre) { this.nombre = nombre; }function getEdad() { return this.edad; }function setEdad(edad) { this.edad = edad; }

var bindToUnObj = curry(bind, unObj), getUnObjNombre = bindToUnObj(getNombre), setUnObjNombre = bindToUnObj(setNombre);

setUnObjNombre("Pepito");getUnObjNombre(); // ???

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

Prototipos

Prototipos

• ¿Por qué tan mala fama?

• ¡Es un mecanismo muy sencillo!

• Distinto a otros lenguajes

Prototipos

Un objeto obj:

qué pasa si hacemos:

var obj = {uno: 1, dos: 2};

obj.uno; // 1

Prototipos

var obj = {uno: 1, dos: 2};

obj.uno; // 1

uno 1

dos 2

uno 1

dos 2

obj

obj

Prototipos

Si hacemos:

var obj = {uno: 1, dos: 2};

obj.tres; // undefined

uno 1

dos 2

obj

objuno 1dos 2

Not found! undefined

Prototipos

¿De dónde sale?

var obj = {uno: 1, dos: 2};

obj.toString(); // '[object Object]'

uno 1

dos 2

obj

objuno 1dos 2

Not found! undefined

¿?

Prototipos

obj.toString(); // '[object Object]'

objuno 1

dos 2

prototype Object

toString functionvalueOf function

...Not found! undefined

Object

Prototipos

Teniendo:

auno 1dos 2

prototype b

toString functionvalueOf function

...Not found! undefine

d

Objectbtres 3

cuatro 4prototype Object

Prototipos

auno 1dos 2

prototype b

toString functionvalueOf function

...Not found! undefine

d

Objectbtres 3

cuatro 4prototype Object

a.uno; // 1

Prototipos

auno 1dos 2

prototype b

toString functionvalueOf function

...Not found! undefine

d

Objectbtres 3

cuatro 4prototype Object

a.cuatro; // 4

Prototipos

auno 1dos 2

prototype b

toString functionvalueOf function

...Not found! undefine

d

Objectbtres 3

cuatro 4prototype Object

a.toString; // [object Object]

Prototipos

auno 1dos 2

prototype b

toString functionvalueOf function

...Not found! undefine

d

Objectbtres 3

cuatro 4prototype Object

a.noExiste; // undefined

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!

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(...) y apply(...)

(function() { alert("Hey!"); })();

objeto.metodo();

new MiConstructor();

fn.call({}, "param");

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

Constructores

• Funciones

• Invocación precedida por new

• Su contexto es un objeto recién generado

• return implícito

• La única manera de manipular prototipos

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"

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"

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

Constructores

var b = { uno: 1, dos: 2};

function A() { this.tres = 3; this.cuatro = 4;}

A.prototype = b;

var instancia = new A();instancia.tres; // 3instancia.uno; // 1

Constructores

var b = { uno: 1, dos: 2};

function A() { this.tres = 3; this.cuatro = 4;}

A.prototype = b;

var instancia = new A();instancia.tres; // 3instancia.uno; // 1

instanciauno 1dos 2

proto b

btres 3

cuatro 4proto Object

Constructores

.hasOwnProperty(name)• Distinguir las propiedades heredadas de las propias

• true solo si la propiedad es del objeto

instancia.hasOwnProperty("tres"); // trueinstancia.hasOwnProperty("uno"); // false

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; // ???

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; // ???

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; // ???

Constructores

pepenombre “Pepe”

proto comun

comunempresa “ACME”

proto Object

var pepe = new Empleado("Pepe");

Constructores

pepenombre “Pepe”

proto comun

comunempresa “Googlezone”

proto Object

comun.empresa = "Googlezon";

Constructores

pepenombre “Pepe”

proto comun

comunempresa “Googlezone”

proto Object

antonionombre “Antonio”

proto comun

var antonio = new Empleado("Antonio");

Constructores

pepenombre “Pepe”

proto comun

comunempresa “Googlezone”

proto Object

antonionombre “Antonio”

proto comun

pepe.empresa;

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)!

Intermedio: constructores

¿Cómo hacer que C herede de B que hereda de A?

Intermedio: constructores

¿Cómo hacer que C herede de B que hereda de A?

Cuno 1

proto B

tres 3

proto Object

ABdos 2

proto A

var instancia = new C();instancia.tres; // 3

Intermedio: constructores

function C() { this.uno = 1;}

var instancia = new C();instancia.tres;

Intermedio: constructores

var B = {dos: 2};

function C() { this.uno = 1;}

C.prototype = B;

var instancia = new C();instancia.tres;

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;

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; // !!!

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; // ???

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;

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;

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!

Mecanismos de herencia

Mecanismos de herencia

• Herencia clásica

• Herencia de prototipos

• Mixins (módulos)

• Extra: herencia funcional

Herencia clásica

¿Qué significa?

• Clases!

• El tipo de herencia más común en otros lenguajes

• Encapsulado y visibilidad

Herencia clásica

Vamos a empezar por un constructor:

function MiClase() { // ???}

var instancia = new MiClase();

Herencia clásica

Las propiedades (públicas)

function MiClase() { this.unaPropiedad = "valor"; this.otraPropiedad = "otro valor";}

var instancia = new MiClase();

Herencia clásica

¿Métodos?

Herencia clásica

¿Métodos?function MiClase() { this.unaPropiedad = "valor"; this.otraPropiedad = "otro valor"; this.unMetodo = function() { alert(this.unaPropiedad); }; this.otroMetodo = function() { alert(this.otraPropiedad); };}

var instancia = new MiClase();instancia.otroMetodo();

Herencia clásica

Mejor así:function MiClase() { this.unaPropiedad = "valor"; this.otraPropiedad = "otro valor";}

MiClase.prototype.unMetodo = function() { alert(this.unaPropiedad);};

MiClase.prototype.otroMetodo = function() { alert(this.otraPropiedad);};

var instancia = new MiClase();instancia.otroMetodo();

Herencia clásica

Solo falta... function Superclase() { /* ... */ }

function MiClase() { this.unaPropiedad = "valor"; this.otraPropiedad = "otro valor";}MiClase.prototype = new Superclase();

MiClase.prototype.unMetodo = function() { alert(this.unaPropiedad);};

MiClase.prototype.otroMetodo = function() { alert(this.otraPropiedad);};

Herencia clásica

¿Cómo se pueden crear métodos “de clase”?

MiClase.metodoEstatico("hola!");

Herencia clásica

¿Cómo se pueden crear métodos “de clase”?

¡Los constructores son objetos!

MiClase.metodoEstatico("hola!");

MiClase.metodoEstatico = function(cadena) { console.log("metodoEstatico:", cadena);}

Herencia clásica: Cuidado!

Este esquema tiene varios problemas:

Herencia clásica: Cuidado!

Este esquema tiene varios problemas:

• ¡No es fácil invocar al super constructor!

Herencia clásica: Cuidado!

function Animal(especie) { this.especie = especie;}

Animal.prototype.getEspecie = function() { return this.especie;}

function Perro(raza) { this.raza = raza;}Perro.prototype = new Animal();

Perro.prototype.describir = function() { return this.getEspecie() + ", de raza " + this.raza;}

function Animal(especie) { this.especie = especie;}

Animal.prototype.getEspecie = function() { return this.especie;}

function Perro(raza) { // ??? this.raza = raza;}Perro.prototype = new Animal();

Perro.prototype.describir = function() { return this.getEspecie() + ", de raza " + this.raza;}

Herencia clásica: Cuidado!

function Animal(especie) { this.especie = especie;}

Animal.prototype.getEspecie = function() { return this.especie;}

function Perro(raza) { Animal("perro"); this.raza = raza;}Perro.prototype = new Animal();

Perro.prototype.describir = function() { return this.getEspecie() + ", de raza " + this.raza;}

Herencia clásica: Cuidado!

function Animal(especie) { this.especie = especie;}

Animal.prototype.getEspecie = function() { return this.especie;}

function Perro(raza) { Animal("perro"); this.raza = raza;}Perro.prototype = new Animal();

Perro.prototype.describir = function() { return this.getEspecie() + ", de raza " + this.raza;}

Herencia clásica: Cuidado!

crea glob. especie!

function Animal(especie) { this.especie = especie;}

Animal.prototype.getEspecie = function() { return this.especie;}

function Perro(raza) { this = new Animal("perro"); this.raza = raza;}Perro.prototype = new Animal();

Perro.prototype.describir = function() { return this.getEspecie() + ", de raza " + this.raza;}

Herencia clásica: Cuidado!

function Animal(especie) { this.especie = especie;}

Animal.prototype.getEspecie = function() { return this.especie;}

function Perro(raza) { this = new Animal("perro"); this.raza = raza;}Perro.prototype = new Animal();

Perro.prototype.describir = function() { return this.getEspecie() + ", de raza " + this.raza;}

Herencia clásica: Cuidado!

ERROR!

function Animal(especie) { this.especie = especie;}

Animal.prototype.getEspecie = function() { return this.especie;}

function Perro(raza) { Animal.call(this, "perro"); this.raza = raza;}Perro.prototype = new Animal();

Perro.prototype.describir = function() { return this.getEspecie() + ", de raza " + this.raza;}

Herencia clásica: Cuidado!

Herencia clásica: Cuidado!

Este esquema tiene varios problemas:

• ¡No es fácil invocar al super constructor!

• No es fácil encapsular

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

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!

Herencia clásica: Cuidado!

function Superclase() { operacionMuyCostosa(); alert(“Oh, no!”);}

function MiClase() { // ...}MiClase.prototype = new Superclase();

Herencia clásica?

¿Qué se puede hacer?

Herencia clásica?

Hay dos enfoques:

Herencia clásica?

Hay dos enfoques:

• El simple

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;}

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;

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!

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

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; };

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; };

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

Herencia clásica?

Hay dos enfoques:

• El simple

• El cómodo

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!"); }});

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!"); }});

Intermedio: klass.js

¡Rellena los huecos!

var Class = function(){};

Class.extend = function(prop) { var _super = this.prototype;

// ...

return Klass;};

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!

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”

Herencia de prototipos

Vamos a cambiar de marcha...

Herencia de prototipos

Vamos a cambiar de marcha...

¡Ahora, sin clases!

Herencia de prototipos

Vamos a cambiar de marcha...

¡Ahora, sin clases!

¿¿Cómo puede haber POO sin clases??

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

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!

Herencia de prototipos

var benito = { nombre: "Benito", edad: 36, profesión: "jardinero", saludar: function() { alert("Buen día!"); }, envejecer: function() { this.edad += 1; }};

Herencia de prototipos

var benito = { nombre: "Benito", edad: 36, profesión: "jardinero", saludar: function() { alert("Buen día!"); }, envejecer: function() { this.edad += 1; }};

var gonzalo = clone(benito);gonzalo.nombre = "Gonzalo";gonzalo.profesion = "carpintero";gonzalo.saludar(); // Buen día!

Herencia de prototipos

var benito = { nombre: "Benito", edad: 36, profesión: "jardinero", saludar: function() { alert("Buen día!"); }, envejecer: function() { this.edad += 1; }};

var gonzalo = clone(benito);gonzalo.nombre = "Gonzalo";gonzalo.profesion = "carpintero";gonzalo.saludar(); // Buen día!

Herencia de prototipos

También se puede generalizarvar 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";

Herencia de prototipos

¡Así de simple!

function clone(obj) { function F(){} F.prototype = obj; return new F();}

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

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!

Herencia de prototipos

¿Cuál uso?

Herencia de prototipos

¿Cuál uso?

¡Las dos!

Intermedio: prototipos

¿Qué significa?

objeto.propiedad;

Intermedio: prototipos

¿Qué significa?

“Accede a la propiedad propiedad del objeto objeto”

objeto.propiedad;

Intermedio: prototipos

¿Qué significa?

“Accede a la propiedad propiedad del objeto objeto. Si no la encuentras, sigue buscando por la cadena de prototipos.”

objeto.propiedad;

Intermedio: prototipos

¿Qué significa?

objeto.propiedad = 1;

Intermedio: prototipos

¿Qué significa?

“Guarda el valor 1 en la propiedad propiedad del objeto objeto.”

objeto.propiedad = 1;

Intermedio: prototipos

¿Qué significa?

“Guarda el valor 1 en la propiedad propiedad del objeto objeto. Si no la encuentras, créala!”

objeto.propiedad = 1;

Intermedio: prototipos

peso 1

proto Object

plumaplomoproto pluma

var pluma = { peso: 1};

var plomo = clone(pluma);

Intermedio: prototipos

peso 1

proto Object

plumaplomoproto pluma

plomo.peso; // 1

Intermedio: prototipos

peso 1

proto Object

plumaplomoproto pluma

peso 100

plomo.peso = 100;

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.

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;

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;

¡Se acabaron los prototipos!

Clausuras

Sólo una idea importante más: ámbitos

Clausuras

• Una idea sencilla, pero difícil de explicar

• Están por todas partes

• Consecuencia natural del lenguaje

• ¡Muy útiles!

Clausuras

function clausurator() { var a = 1; return function() { return a; };}

Clausuras

function clausurator() { var a = 1; return function() { return a; };}

var fn = clausurator();typeof fn;

Clausuras

function clausurator() { var a = 1; return function() { return a; };}

var fn = clausurator();fn(); // ???

Clausuras

function clausurator() { var a = 1; return function() { return a; };}

var fn = clausurator();fn();

fn = function() { return a;}

Clausuras

function clausurator() { var a = 1; return function() { return a; };}

var fn = clausurator();fn();

fn = function() { return a;}

Clausuras

function clausurator() { var a = 1; return function() { return a; };}

var fn = clausurator();fn();

fn = function() { return a;}

Clausuras

function clausurator() { var a = 1; return function() { return a; };}

var fn = clausurator();fn();

fn = function() { return a;}

a = 1;

Clausuras

Otro caso:function makeContador() { var i = 0; return function() { return i++; }}

var contador1 = makeContador();contador1(); // ???contador1(); // ???

var contador2 = makeContador();contador2(); // ???

Clausuras

Otro caso:function makeContador() { var i = 0; return function() { return i++; }}

var contador1 = makeContador();contador1(); // 0contador1(); // 1

var contador2 = makeContador();contador2(); // 0

Clausuras

function makeContador() { var i = 0; return function() { return i++; }}

var contador1 = makeContador();contador1(); // 0contador1(); // 1

var contador2 = makeContador();contador2(); // 0

contador1 = function() { return i++;}

i = 0;

Clausuras

function makeContador() { var i = 0; return function() { return i++; }}

var contador1 = makeContador();contador1(); // 0contador1(); // 1

var contador2 = makeContador();contador2(); // 0

contador1 = function() { return i++;}

i = 1;

Clausuras

function makeContador() { var i = 0; return function() { return i++; }}

var contador1 = makeContador();contador1(); // 0contador1(); // 1

var contador2 = makeContador();contador2(); // 0

contador1 = function() { return i++;}

i = 1;

contador2 = function() { return i++;}

i = 0;

Clausuras

function interesante() { var algo = 0; return { get: function() { return algo; }, set: function(valor) { return algo = valor; } };}

var obj = interesante();obj.get(); // ???obj.set("hola!");obj.get(); // ???

Clausuras

• bind: fija una función a un contexto

function bind(ctx, fn) { return function() { return fn.apply(ctx, arguments); }}

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

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!

Intermedio: herencia funcional

function PseudoConstructor() { var self = {}; self.propiedad = "valor"; return self;}

var pseudoInstancia = PseudoConstructor();pseudoInstancia.propiedad; // "valor"

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

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

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

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

Intermedio: herencia funcional

¿Y para heredar?

Intermedio: herencia funcional

¿Y para heredar?function A() { var self = {}; self.uno = 1; return self;}

function B() { var self = A(); self.dos = 2; return self;}

var b = B();b.uno; // 1

Intermedio: herencia funcional

¿Cómo llamar al supermétodo?

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;}

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

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!

Programación Funcional

made with love by Redradix (www.redradix.com)

¿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

Funciones de orden superior

Funciones que devuelven funciones

• curry

• bind

• ¡Muchas otras!

Funciones de orden superior

Algunas de las más útiles:

• throttle

• debounce

• once

• after

• compose

• memoize

throttle

Controlar la frecuencia de invocación

• La función se invocará como máximo una vez

• Durante el periodo de tiempo especificado

throttle

var counter = 0, inc = function() { counter++; };

inc = throttle(inc, 10);

for (var i=100000; i--;) { inc();}

alert(counter); // ~6

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

debounce

Ejecutar la función cuando se deje de llamar

• La llamada se pospone hasta que pasen x ms

• Desde la última invocación

debounce

var counter = 0, inc = function() { counter++; alert(counter); };

inc = debounce(inc, 1000);

for (var i=100000; i--;) { inc();}

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

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

once

function once(fn) { var executed = false; return function() { if (!executed) { executed = true; return fn.apply(this, arguments); } }}

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

after

function after(fn, n) { var times = 0; return function() { times++; if (times % n == 0) { return fn.apply(this, arguments); } }}

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

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; }}

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

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

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]; }}

Asincronía

JS es, por naturaleza, asíncrono

• Eventos

• AJAX

• Carga de recursos

Asincronía

¿Qué significa asíncrono?

function asincrona() { var random = Math.floor(Math.random() * 100); setTimeout(function() { return random; }, random);}

Asincronía

¿Cómo devuelvo el valor random desde dentro?

function asincrona() { var random = Math.floor(Math.random() * 100); setTimeout(function() { return random; }, random);}

Asincronía

function asincrona(callback) { var random = Math.floor(Math.random() * 1000); setTimeout(function() { callback(random); }, random);}

asincrona(function(valor) { alert(valor);});

Asincronía

function asincrona(callback) { var random = Math.floor(Math.random() * 1000); setTimeout(function() { callback(random); }, random);}

asincrona(function(valor) { alert(valor);});

Asincronía

function asincrona(callback) { var random = Math.floor(Math.random() * 1000); setTimeout(function() { callback(random); }, random);}

asincrona(function(valor) { alert(valor);});

Asincronía

Promesas

• Otra forma de escribir código asíncrono

• Más fácil de manipular

• Más fácil de combinar

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

Promesas

function onSuccess(data) { /* ... */ }

function onFailure(e) { /* ... */}

var promesa = $.get('/mydata');promesa.then(onSuccess, onFailure);

Promesas

promise.then(onSuccess [, onFailure])• En caso de éxito, se invoca a onSuccess con el valor

• En caso de error, se invoca a onFailure

• Devuelve, a su vez, una promesa

Promesas

¿Para qué sirven?

• Dar un aspecto más coherente al código

• Hacer más explícito el flow

• Gestionar los errores en cascada

Promesas

Parse.User.logIn("user", "pass", { success: function(user) { query.find({ success: function(results) { results[0].save({ key: value }, { success: function(result) { // El objeto se guardó. }, error: function(result, error) { // Error. } }); }, error: function(error) { // Error. } }); }, error: function(user, error) { // Error. }});

Promesas

Parse.User.logIn("user", "pass").then(function(user) { return query.find();}).then(function(results) { return results[0].save({ key: value });}).then(function(result) { // El objeto se guardó.}, function(error) { // Error.});

Promesas

Parse.User.logIn("user", "pass").then(function(user) { return query.find();}).then(function(results) { return results[0].save({ key: value });}).then(function(result) { // El objeto se guardó.}, function(error) { // Error.});

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

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

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!");});

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...");});

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

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

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

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

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! */});

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! */});

Promesas

¿Cómo creo una promesa?

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

Promesas

Promesa DiferidoRepresenta un valor futuro

Controla la generación del valor

onSuccess resolve(valor)

onFailure reject(error)

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

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

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

Promesas

Deferred#promise()• Devuelve la promesa asociada al diferido

Promesas

Deferred#then(onSuccess, onFailure)• Exactamente igual que hacer: deferred.promise().then(...);

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

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

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

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

Promesas

R.Deferred.when(1, 2, 3).then(function(a, b, c) { console.log(a, b, c); // 1 2 3});

Promesas

var p1 = new R.Deferred(), p2 = new R.Deferred(), p3 = new R.Deferred();

R.Deferred.when(p1, p2, p3).then(function(a, b, c) { console.log(a, b, c); // 1 2 3});

p1.resolve(1);p2.resolve(2);p3.resolve(3);

Promesas

/* Homogeneizar */

var promesaOValor = noSeQueDevuelve();

R.Deferred.when(promesaOValor).then(function(valor) { console.log(valor);});

Promesas

/* Homogeneizar */

var valor = 4, promesa = new R.Deferred();

R.Deferred.when(valor, promesa).then(function(a, b) { console.log(a, b); // 4, 5});

promesa.resolve(5);

Promesas

var valor = 4, promesa = new R.Deferred();

R.Deferred.when(valor, promesa).then(function(a, b) { console.log(a, b);}, function(e) { alert("Oh, no!");});

promesa.reject("No funciono");

Promesas

Implementa R.Deferred.when()• tema2/when/index.html

Patrones y principios de diseño

made with love by Redradix (www.redradix.com)

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

SRP

Es común ver cosas como esta:

$.ajax({ ... }) .success(function() { cambioEnInterfaz(); mostrarModal(); if ($("#elemento").value() == "Ok") { /* ... */ } globalSeHaGuardado = true; }) .error(function() { // ... });

SRP

O como esta:

var Widget = Class.extend({ onClick: function() { ... }, guardar: function() { ... }, render: function() { ... }, mostrarError: function() { ... }});

SRP

var Widget = Model.extend({ guardar: function() { ... }});

var WidgetView = View.extend({ render: function() { ... }});

var WidgetController = Controller.extend({ onClick: function() { ... }});

var ErrorAlert = ModalWindow.extend({ mostrarError: function() { ... }});

SRP

Caso práctico: masonry.js

• https://github.com/desandro/masonry/blob/master/jquery.masonry.js

• en el método _create (línea 102)...

SRP

!!!!//!sets!up!widget!!!!_create!:!function(!options!)!{!!!!!!//![...]

!!!!!!//!get!original!styles!in!case!we!re3apply!them!in!.destroy()!!!!!!var!elemStyle!=!this.element[0].style;!!!!!!this.originalStyle!=!{!!!!!!!!//!get!height!!!!!!!!height:!elemStyle.height!||!''!!!!!!};!!!!!!//!get!other!styles!that!will!be!overwritten!!!!!!//![...]

3s.isFluid!=!this.options.columnWidth!&&!typeof!this.options.columnWidth!===!'function';

!!!!!!//!add!masonry!class!first!time!around!!!!!!var!instance!=!this;!!!!!!setTimeout(!function()!{!!!!!!!!instance.element.addClass('masonry');!!!!!!},!0!);!!!!!!!!!!!!//!bind!resize!method!!!!!!if!(!this.options.isResizable!)!{!!!!!!!!$(window).bind(!'smartresize.masonry',!function()!{!!!!!!!!!!!instance.resize();!!!!!!!!});!!!!!!}

SRP

!!!!//!sets!up!widget!!!!_create!:!function(!options!)!{!!!!!!//![...]

!!!!!!//!get!original!styles!in!case!we!re3apply!them!in!.destroy()!!!!!!var!elemStyle!=!this.element[0].style;!!!!!!this.originalStyle!=!{!!!!!!!!//!get!height!!!!!!!!height:!elemStyle.height!||!''!!!!!!};!!!!!!//!get!other!styles!that!will!be!overwritten!!!!!!//![...]

3s.isFluid!=!this.options.columnWidth!&&!typeof!this.options.columnWidth!===!'function';

!!!!!!//!add!masonry!class!first!time!around!!!!!!var!instance!=!this;!!!!!!setTimeout(!function()!{!!!!!!!!instance.element.addClass('masonry');!!!!!!},!0!);!!!!!!!!!!!!//!bind!resize!method!!!!!!if!(!this.options.isResizable!)!{!!!!!!!!$(window).bind(!'smartresize.masonry',!function()!{!!!!!!!!!!!instance.resize();!!!!!!!!});!!!!!!}

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

SRP

Caso práctico: BrowserQuest

• https://github.com/mozilla/BrowserQuest/blob/master/client/js/chest.js

SRP

!!!!var!Chest!=!Entity.extend({!!!!!!!!init:!function(id,!kind)!{!!!!! !!!!this._super(id,!Types.Entities.CHEST);!!!!!!!!},!!!!!!!!!!!!getSpriteName:!function()!{!!!!!!!!!!!!return!"chest";!!!!!!!!},!!!!!!!!!!!!isMoving:!function()!{!!!!!!!!!!!!return!false;!!!!!!!!},!!!!!!!!!!!!open:!function()!{!!!!!!!!!!!!if(this.open_callback)!{!!!!!!!!!!!!!!!!this.open_callback();!!!!!!!!!!!!}!!!!!!!!},!!!!!!!!!!!!onOpen:!function(callback)!{!!!!!!!!!!!!this.open_callback!=!callback;!!!!!!!!}!!!!});

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

SRP

Caso práctico: Backbone.js

• https://github.com/documentcloud/backbone/blob/master/backbone.js

• en Backbone.Model, línea 179...

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”)

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

Principios de diseño

• Tell, Don’t Ask

- “Dime lo que necesitas”

- Claridad y expresividad

- Encapsular las comprobaciones

Tell, Don’t Ask

Los síntomas:

!!!!!!!!!!!!!!!!!!!!if!(typeof(variables)!===!'object'!&&!!Array.isArray(variables))!{!!!!!!!!!!!!!!!!!!!!!!!!variables!=!Object.keys(variables).map(function!(k)!{!!!!!!!!!!!!!!!!!!!!!!!!!!!!var!value!=!variables[k];

!!!!!!!!!!!!!!!!!!!!!!!!!!!!if!(!!(value!instanceof!tree.Value))!{!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!if!(!!(value!instanceof!tree.Expression))!{!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!value!=!new(tree.Expression)([value]);!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!}!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!value!=!new(tree.Value)([value]);!!!!!!!!!!!!!!!!!!!!!!!!!!!!}!!!!!!!!!!!!!!!!!!!!!!!!!!!!return!new(tree.Rule)('@'!+!k,!value,!false,!0);!!!!!!!!!!!!!!!!!!!!!!!!});!!!!!!!!!!!!!!!!!!!!!!!!frames!=![new(tree.Ruleset)(null,!variables)];!!!!!!!!!!!!!!!!!!!!}

Tell, Don’t Ask

Los síntomas:

!!!!!!!!!!!!!!!!!!!!if!(typeof(variables)!===!'object'!&&!!Array.isArray(variables))!{!!!!!!!!!!!!!!!!!!!!!!!!variables!=!Object.keys(variables).map(function!(k)!{!!!!!!!!!!!!!!!!!!!!!!!!!!!!var!value!=!variables[k];

!!!!!!!!!!!!!!!!!!!!!!!!!!!!if!(!!(value!instanceof!tree.Value))!{!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!if!(!!(value!instanceof!tree.Expression))!{!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!value!=!new(tree.Expression)([value]);!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!}!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!value!=!new(tree.Value)([value]);!!!!!!!!!!!!!!!!!!!!!!!!!!!!}!!!!!!!!!!!!!!!!!!!!!!!!!!!!return!new(tree.Rule)('@'!+!k,!value,!false,!0);!!!!!!!!!!!!!!!!!!!!!!!!});!!!!!!!!!!!!!!!!!!!!!!!!frames!=![new(tree.Ruleset)(null,!variables)];!!!!!!!!!!!!!!!!!!!!}

Tell, Don’t Ask

Programación:

• Estructurada: adquiere info y toma decisiones

• OO: manda a los objetos hacer cosas

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

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!

Tell, Don’t Ask

if (usuario.primerLogin) { usuario.mostrarMensajeBienvenida();} else { usuario.mostrarSaludo();}

Tell, Don’t Ask

var Usuario = Class.extend({ saludar: function() { if (this.primerLogin) { this.mostrarMensajeBienvenida(); } else { this.mostrarSaludo(); } }});

// y después...

usuario.saludar();

Tell, Don’t Ask

function comprobarTimeout(respuesta) { if ((Date.now() - respuesta.start) > 10000) { respuesta.notificarTimeout(); }}

Tell, Don’t Ask

var Respuesta = Class.extend({ comprobarTimeout: function() { if ((Date.now() - this.start) > 10000) { this.notificarTimeout(); } }});

// y después...

respuesta.comprobarTimeout();

Tell, Don’t Ask

var elementos = miColeccion.getItems();for (var i=0; i<elementos.length; i++) { var elemento = elementos[i]; console.log(elemento.nombre);}

Tell, Don’t Ask

miColeccion.forEach(function(e) { console.log(e.nombre);});

Tell, Don’t Ask

var elemento = new Elemento("hola", 12);var lista = miColeccion.getItems();lista.addElementAt(elemento.getOrder(), elemento);

Tell, Don’t Ask

var elemento = new Elemento("hola", 12);miColeccion.add(elemento);

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!

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

Principios de diseño

• S.O.L.I.D.

- Single Responsibility

- Open-Closed

- Liskov Substitution

- Interface Segregation

- Dependency Inversion

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

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

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

Open-Closed

Pretende:

• Promover el uso de abstracciones

• Código modular y flexible ante el cambio

• Evitar un torrente de cambios en cascada!

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

Open-Closed

var Canvas = Class.extend({ render: function(figura) { if (figura instanceof Triangulo) { // ... } else if (figura instanceof Cuadrado) { // ... } }});

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) { ...}});

Open-Closed

Caso práctico: three.js

• https://github.com/mrdoob/three.js/blob/master/src/renderers/CanvasRenderer.js

• método render, línea 225

Open-Closed

if!(!element!instanceof!THREE.RenderableParticle!)!{

//!...

}!else!if!(!element!instanceof!THREE.RenderableLine!)!{

//!...

}!else!if!(!element!instanceof!THREE.RenderableFace3!)!{

//!...

}!else!if!(!element!instanceof!THREE.RenderableFace4!)!{

//!...}

Open-Closed

Caso práctico: jasmine.js

•https://github.com/pivotal/jasmine/blob/master/src/core/Reporter.js

Open-Closed

/**!No3op!base!class!for!Jasmine!reporters.!*!*!@constructor!*/jasmine.Reporter!=!function()!{};

//noinspection!JSUnusedLocalSymbolsjasmine.Reporter.prototype.reportRunnerStarting!=!function(runner)!{};

//noinspection!JSUnusedLocalSymbolsjasmine.Reporter.prototype.reportRunnerResults!=!function(runner)!{};

//...

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

Sustitución de Liskov

var Animal = Class.extend({ caminar: function() { /*...*/ }, comer: function() { /* .. */ }});

Sustitución de Liskov

var Animal = Class.extend({ caminar: function() { /*...*/ }, comer: function() { /* ... */ }});

var Serpiente = Animal.extend({ /* ... */});

var s = new Serpiente();s.caminar();

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”

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

Segregación de la intefaz

var Modal = Class.extend({ show: function() { ... }, hide: function() { ... }});

Segregación de la intefaz

var Modal = Class.extend({ show: function() { ... }, hide: function() { ... }});

var Timer = Class.extend({ setTimer: function(time) { ... }, startTimer: function() { ... }, onTimeout: function() { ... }});

Segregación de la intefaz

var Modal = Class.extend({ show: function() { ... }, hide: function() { ... }});

var Timer = Class.extend({ setTimer: function(time) { ... }, startTimer: function() { ... }, onTimeout: function() { ... }});

var TimedModal = Modal.extend({ init: function() { this.setTimer(100); this.onTimeout = this.hide; this.show(); this.startTimer(); }});

Segregación de la intefaz

var Timer = Class.extend({ setTimer: function(time) { ... }, startTimer: function() { ... }, onTimeout: function() { ... }});

var Modal = Timer.extend({ show: function() { ... }, hide: function() { ... }});

var TimedModal = Modal.extend({ init: function() { this.setTimer(100); this.onTimeout = this.hide; this.show(); this.startTimer(); }});

Segregación de la intefaz

var WidgetView = Class.extend({ init: function(cont) { var cbind = curry(bind, cont); $("#E1").click(cbind(cont.onE1Click)); }});

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

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)); // ... }});

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)); // ... }});

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)); // ... }});

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.

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

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

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) { }});

Dependency Inversion

var ServerStore = Store.extend({ save: function(data, success, error, complete) { $.post(this.url, data) .success(success) .error(error) .complete(complete); }});

var db = {};var MemStore = Store.extend({ save: function(data, success, error, complete) { db[this.url] = data; complete(); success(); }});

Dependency Inversion

Caso práctico: backbone.js

• https://github.com/documentcloud/backbone/blob/master/backbone.js

• Backbone.Model#fetch, línea 335

Dependency Inversion

!!!!fetch:!function(options)!{!!!!!!options!=!options!?!_.clone(options)!:!{};!!!!!!var!model!=!this;!!!!!!var!success!=!options.success;!!!!!!options.success!=!function(resp,!status,!xhr)!{!!!!!!!!if!(!model.set(model.parse(resp,!xhr),!options))!!!!!!!!!!return!false;!!!!!!!!if!(success)!success(model,!resp,!options);!!!!!!};!!!!!!return!this.sync('read',!this,!options);!!!!},

Patrones de organización

Patrones de organización

• Parámetros con nombre/por defecto

• Módulos y namespaces

• Control de acceso

• Mixins

Parámetros con nombre

function ajax(url, data, method, success, error, complete) { url || url = "/"; data || data = {}; method || method = "POST"; // ...}

ajax("/", {}, "GET", function(){ ... }, function() { ... }, function() { ... });

Parámetros con nombre

function ajax(options) { var url = options.url || "/", data = options.data || {}, method = options.method || "POST"; //...}

ajax({data: [1, 2], complete: function() { ... }});

Parámetros por defecto

function ajax(options) { var fn = function() {}, defaults = {url: "/", data: [], method: "POST", success: fn, error: fn, complete: fn}; options = merge(defaults, options); // ...}

ajax({data: [1, 2], complete: function() { ... }});

Parámetros por defecto

backbone.js:749

!!!!reset:!function(models,!options)!{!!!!!!for!(var!i!=!0,!l!=!this.models.length;!i!<!l;!i++)!{!!!!!!!!this._removeReference(this.models[i]);!!!!!!}!!!!!!this._reset();!!!!!!if!(models)!this.add(models,!_.extend({silent:!true},!options));!!!!!!if!(!options!||!!options.silent)!this.trigger('reset',!this,!options);!!!!!!return!this;!!!!},

Intermedio: merge

¿Cómo sería esa función merge?

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;}

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

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!

Módulos y namespaces

function miHelper() { // ...}

var miVariableTemporal = 0;var estadoLocal = {};

Módulos y namespaces

function sandbox() { function miHelper() { // ... }

var miVariableTemporal = 0; var estadoLocal = {}; }

Módulos y namespaces

(function sandbox() { function miHelper() { // ... }

var miVariableTemporal = 0; var estadoLocal = {}; }())

Módulos y namespaces

(function sandbox() { function miHelper() { // ... }

var miVariableTemporal = 0; var estadoLocal = {}; }())

Módulos y namespaces

function miFuncionUtil() { // ...}

function miGranMetodo() { // ...}

function miEstupendoHelper() { // ...}

Módulos y namespaces

function aux() { }var state = "off";

function miFuncionUtil() { // ...}

function miGranMetodo() { // ...}

function miEstupendoHelper() { // ...}

Módulos y namespaces

(function() { function aux() { } var state = "off";

function miFuncionUtil() { }

function miGranMetodo() { }

function miEstupendoHelper() { }}())

Módulos y namespaces

var Modulo = (function() { function aux() { } var state = "off";

function miFuncionUtil() { }

function miGranMetodo() { } return { miFuncionUtil: miFuncionUtil, miGranMetodo: miGranMetodo };}());

Módulos y namespaces

var Modulo = (function() { function aux() { } var state = "off";

function miFuncionUtil() { }

function miGranMetodo() { } return { miFuncionUtil: miFuncionUtil, miGranMetodo: miGranMetodo };}());

Módulos y namespaces

Modulo.miFuncionUtil();

Módulos y namespaces

var Modulo = {};

(function(Modulo) { function aux() { } var state = "off";

Modulo.miFuncionUtil = function() { }

Modulo.miGranMetodo = function() { }

}(Modulo));

Módulos y namespaces

var Modulo = {};

(function(Modulo) { function aux() { } var state = "off"; Modulo.miFuncionUtil = function() { }}(Modulo));

(function(Modulo) { Modulo.miGranMetodo = function() { }}(Modulo));

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 || {}));

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 || {}));

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

Módulos y namespaces

var Modulo = (function(Modulo) { _private.password = "1234";}(Modulo || {}));

var Modulo = (function(Modulo) { Modulo.login = function(pass) { return pass == _private.password; };}(Modulo || {}));

Modulo._private; // undefined

Módulos y namespaces

var Modulo = (function (Modulo) { var _private = Modulo._private = (Modulo._private || {}), _seal = Modulo._seal = (Modulo._seal || function () { delete Modulo._private; delete Modulo._seal; });

// acceso permanente a _private y _seal return Modulo;}(Modulo || {}));

Módulos y namespaces

var Modulo = (function (Modulo) { var _private = Modulo._private; // acceso a _private

}(Modulo || {}));

Módulos y namespaces

Modulo._seal();

Intermedio: mejorar klass.js

Convierte klass.js en un módulo Class

Módulos y namespaces

Submódulos: muy fácil!

var MiLibreria = {};

MiLibreria.eventos = (function(eventos) { eventos.on = function() { }; eventos.off = function() { }; return eventos;}(MiLibreria.eventos));

Módulos y namespaces

Según crece la aplicación...

Módulos y namespaces

Según crece la aplicación...

var MiLibreria = MiLibreria || {};

Módulos y namespaces

Según crece la aplicación...

var MiLibreria = MiLibreria || {};MiLibreria.widgets = MiLibreria.widgets || {};

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 || {}));

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({ ... });});

Intermedio: namespace

¿Como sería la función MiLib.namespace?

Intermedio: namespace

¿Como sería la función MiLib.namespace?

var MiLib = (function(my) { my.namespace = function(string, sandbox) { // ??? }; return my;}(MiLib || {}));

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 || {}));

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

Mixins

var Mixin = function() {};Mixin.prototype = { inspect: function() { var output = []; for(key in this) { output.push(key + ': ' + this[key]); } return output.join(', '); }};

Mixins

var Persona = Class.extend({ init: function(nombre) { this.nombre = nombre; }});

augment(Persona, Mixin);

var pepito = new Persona("Pepito");pepito.inspect();

Mixins

function augment(target, source) { for (var prop in source.prototype) { if(!target.prototype[prop]) { target.prototype[prop] = source.prototype[prop]; } }}

Intermedio: Mejores Mixins

Lo podemos hacer mejor!

• Mejor integración con klass.js

• Callbacks de inclusión (estilo ruby)

Intermedio: Mejores Mixins

var StaticModule = { propiedadDeClase: "Soy una propiedad de clase", included: function(klass) { console.log("Includido!"); }};

var InstanceModule = { diHola: function() { alert("HOLA!"); }, mixed: function(klass) { console.log("Mezclado!"); }};

var MiClase = Class.extend({ init: function() { }});

MiClase.include(StaticModule);MiClase.mixin(InstanceModule);

Patrones de creación de objetos

Factoría

Delegar la creación de un objeto

• Elegir el constructor dinámicamente

• Procesos de construcción complejos

• Desacoplar detalles de implementación

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 Title”

Factoría

var GlobalConfig = {locale: "es"};

Factoría

var GlobalConfig = {locale: "es"};

I18n.withCurrentLocale = function() { var instance = new this; instance.locale = GlobalConfig.locale; return instance;};

Factoría

var i18n = I18n.withCurrentLocale();alert(i18n.translate('header.title'));

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

Factoría

Controlar las instanciasvar 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;}());

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

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

Singleton

Clase con una única instancia

• Un tipo peculiar de Factoría

• Cuando no tiene sentido más de una instancia

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

Patrones de abstracción

Patrones de abstracción

• Iteradores

• Decorador

• Fachada

• Estrategia

• Inyección de dependencias

• Proxy

Iteradores

Recorrer una colección

• Sin revelar detalles de implementación

• Mayor control sobre la iteración

Iteradores

var ListadoAlumnos = Class.extend({ init: function() { this.alumnos = []; }, add: function(nombre, ciudad) { this.alumnos.push({ nombre: nombre, ciudad: ciudad }); }});

Iteradores

var ListadoAlumnos = Class.extend({ init: function() { this.alumnos = []; }, add: function(nombre, ciudad) { this.alumnos.push({ nombre: nombre, ciudad: ciudad }); }});

var lista = new ListadoAlumnos();lista.add("Gonzalo", "Madrid");lista.add("Gerardo", "Madrid");lista.add("Guzman", "Valencia");

Iteradores

var lista = new ListadoAlumnos();lista.add("Gonzalo", "Madrid");lista.add("Gerardo", "Madrid");lista.add("Guzman", "Valencia");

var alumnos = lista.alumnos;for (var i=0; i<alumnos.length; i++) { if (alumnos[i].ciudad == "Madrid") { console.log(alumnos[i].nombre); }}

Iteradores

var lista = new ListadoAlumnos();lista.add("Gonzalo", "Madrid");lista.add("Gerardo", "Madrid");lista.add("Guzman", "Valencia");

var alumnos = lista.alumnos;for (var i=0; i<alumnos.length; i++) { if (alumnos[i].ciudad == "Madrid") { console.log(alumnos[i].nombre); }}

Iteradores

var ListadoAlumnos = Class.extend({ init: function() { this.alumnos = []; }, add: function(nombre, ciudad) { this.alumnos.push({ nombre: nombre, ciudad: ciudad }); }, forEachIn: function(ciudad, fn) { for (var i=0; i<this.alumnos.length; i++) if (this.alumnos[i].ciudad == ciudad) { fn(this.alumnos[i].nombre); } }});

Iteradores

var lista = new ListadoAlumnos();lista.add("Gonzalo", "Madrid");lista.add("Gerardo", "Madrid");lista.add("Guzman", "Valencia");

lista.forEachIn("Madrid", function(n) { console.log(n);});

Iteradores

var ListadoAlumnos = Class.extend({ init: function() { this.alumnos = []; }, add: function(nombre, ciudad) { this.alumnos.push({ nombre: nombre, ciudad: ciudad }); }, next: function() { this.index = this.index || 0; return this.alumnos[this.index++]; }});

Iteradores

var lista = new ListadoAlumnos();lista.add("Gonzalo", "Madrid");lista.add("Gerardo", "Madrid");lista.add("Guzman", "Valencia");

var alumno;while(alumno = lista.next()) { alert(alumno.nombre);}

Iteradores

var Iterator = Class.extend({ init: function(collection) { this.col = collection; this.pos = 0; }, next: function() { return this.col[this.pos++]; }});

var ListadoAlumnos = Class.extend({ init: function() { this.alumnos = []; }, add: function(nombre, ciudad) { this.alumnos.push({ nombre: nombre, ciudad: ciudad }); }, getIterator: function() { return new Iterator(this.alumnos); }});

Iteradores

var lista = new ListadoAlumnos();lista.add("Gonzalo", "Madrid");lista.add("Gerardo", "Madrid");lista.add("Guzman", "Valencia");

var alumno, iter = lista.getIterator();while(alumno = iter.next()) { if (alumno.ciudad == "Madrid") { console.log(alumno.nombre); }}

Iteradores

var Fibonacci = Class.extend({ init: function() { this.a = 0; this.b = 1; }, next: function() { var next = this.a; this.a = this.b; this.b = this.b + next; return next; }});

var iter = new Fibonacci();for (var i=10; i--;) { console.log(iter.next());}

Decorador

Añadir funcionalidad dinámicamente

• Evitar subclases innecesarias

• Modificar comportamientos “en vivo”

• Manteniendo la interfaz/contrato! (transparente)

• Muy apropiada para JS

Decorador

var Producto = Class.extend({ init: function(precio) { this.precio = precio; }, getPrecio: function() { return this.precio; }});

Decorador

var Producto = Class.extend({ init: function(precio) { this.precio = precio; }, getPrecio: function() { return this.precio; }});

var ProductoConIVA = Producto.extend({ init: function(precio) { this._super(precio); }, getPrecio: function() { return this._super() * 1.21; }});

Decorador

var ProductoConDescuento = Producto.extend({ init: function(precio) { this._super(precio); }, getPrecio: function() { return this._super() * 0.90; }});

var ProductoConIVAConDescuento = ProductoConIVA.extend({ init: function(precio) { this._super(precio); }, getPrecio: function() { return this._super() * 0.90; }});

Decorador

var ProductoTecnologico = Producto.extend({ });

var ProductoTecnologicoConDescuento = ProductoConDescuento.extend({});

var ProductoTecnologicoConIVA = ProductoConIVA.extend({});

var ProductoTecnologicoConIVAConDescuento = ProductoConIVAConDescuento.extend({});

Decorador

var Producto = Class.extend({ init: function(precio) { this.precio = precio; }, getPrecio: function() { return this.precio; }});

Decorador

var Producto = Class.extend({ init: function(precio) { this.precio = precio; }, getPrecio: function() { return this.precio; }});

var ProductoConIVA = Class.extend({ init: function(producto) { this.producto = producto; }, getPrecio: function() { return this.producto.getPrecio() * 1.21; }});

Decorador

var ProductoConDescuento = Class.extend({ init: function(producto) { this.producto = producto; }, getPrecio: function() { return this.producto.getPrecio() * 0.90; }});

var ProductoTecnologico = Class.extend({ init: function(producto) { this.producto = producto; }, getPrecio: function() { return this.producto.getPrecio() * 0.90; }});

Decorador

var producto = new Producto(20);producto = new ProductoConIVA(producto);producto = new ProductoConDescuento(producto);producto = new ProductoTecnologico(producto);producto.getPrecio(); // 19.602

Decorador

var producto = new Producto(20);producto = new ProductoConIVA(producto);producto = new ProductoConDescuento(producto);producto = new ProductoTecnologico(producto);producto.getPrecio(); // 19.602

ProductoTecnológico

ProductoConDescuentoProductoConIVA

ProductogetPrecio()

getPrecio()

getPrecio()

getPrecio()

Decorador

var producto = new Producto(20);producto = new ProductoConIVA(producto);producto = new ProductoConDescuento(producto);producto = new ProductoTecnologico(producto);producto.getPrecio(); // 19.602

ProductoTecnológico

ProductoConDescuentoProductoConIVA

Producto

(20 * 1.21) * 0.90

20 * 1.21

20

((20 * 1.21) * 0.90) * 0.90

19.602

Decorador

Cuando usar decoradores:

• Modificar el comportamiento manteniendo interfaz

• Añadir post- o preproceso a la salida/entrada de un obj

- Serialización

- Interfaz

• Dentro de factorías

• Intervenir el llamadas a métodos

Fachada

Simplificar el interfaz y desacoplar al cliente

• Patrón muy simple

• El mejor amigo del programador JavaScript

• Casi todas las librerías son fachadas

• Comodidad

Fachada

Ejemplo por excelencia: jQuery

• $(“selector”)• $(“selector”).click(...)• $.ajax• ...

Fachada

Nuestra librería klass.js

• Una capa sobre los mecanismos nativos

• Simplifica el “interfaz”

• Mucho más cómodo que hacerlo a mano

Fachada

var DOMNode = Class.extend({ init: function(tag) { this.el = document.createElement(tag); }, setClass: function(klass) { this.el.className += (" " + klass); }, setId: function(id) { this.el.id = id; }, getElement: function() { return this.el; }});

var node = new DOMNode('div');node.setClass("big");node.setClass("red");node.setId("my-button");document.body.appendChild(node.getElement());

Fachada

DOMNode.create = function(tag, classes, id) { var node = new this(tag); node.setClass(classes.join(" ")); node.setId(id); return node.getElement();}var el = DOMNode.create("div", ["big", "red"], "my-button");document.body.appendChild(el);

Fachada

¿Cuándo usar fachadas?

• Clases muy potentes y complejas

- Simplificar los usos más comunes

• Operaciones con mucha preparación

- XMLHttpRequest

• Código repetitivo

• Para cambios grandes del código

Estrategia

Seleccionar algoritmos dinámicamente

• Dada una familia de algoritmos

• Encapsulados bajo un mismo interfaz

• Elegir el más apropiado

Estrategia

var validate = (function() { var validators = { email: function(value) { return /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\ ".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA -Z\-0-9]+\.)+[a-zA-Z]{2,}))$/.test(value); }, number: function(value) { return /^\d+$/.test(value); } }; return function(data) { var validation = validators[data.type]; return validation && validation(data.value); };}());

validate({type: "email", value: "eliasagc@gmail.com"});

Estrategia

var MoodConsole = Class.extend({ init: function() { this.state = "normal"; this.filters = { normal: new DummyFilter(), relajado: new RelaxedFilter(), cabreado: new AngryFilter() }; }, log: function(msg) { msg = this.filters[this.state].filter(msg); console.log(msg); }, molestar: function() { this.state = "cabreado"; }, masajear: function() { this.state = "relajado"; }, dejarEnPaz: function() { this.state = "normal"; }});

Estrategia

var DummyFilter = Class.extend({ filter: function(s) { return s; }});

var AngryFilter = DummyFilter.extend({ filter: function(s) { return s.toUpperCase() + "!!"; }});

var RelaxedFilter = DummyFilter.extend({ filter: function(s) { return "..." + s + "..."; }});

var moodConsole = new MoodConsole();moodConsole.log("hola!");moodConsole.molestar();moodConsole.log("grrr...");moodConsole.masajear();moodConsole.log("gracias");

Estrategia

¿Cuándo usar estrategias?

• Una misma acción puede tener varios significados

- Según un estado variable (selector de contexto)

- click, touch, una botón “Guardar”, etc..

• Validaciones

• En general: cuando hay que elegir un algoritmo diferente para cada caso

Inyección de dependencias

Selección dinámica de un componente

• Según el principio de Inversión de Dependencias

• El cliente consume siguiendo un interfaz

• La clase concreta se selecciona en tiempo de ejecución

Inyección de dependencias

var View = Class.extend({ init: function(renderer, model) { this.renderer = renderer; this.model = model; }, render: function() { this.renderer.render(this.template, this.model); }});

var HtmlRenderer = Class.extend({ render: function() { ... }});

var XMLRenderer = Class.extend({ render: function() { ... }});

Inyección de dependencias

var miVista = new View(new XMLRenderer('fast'), {});

Proxy

Un objeto hace de interfaz de otro objeto o recurso

• Entre el cliente y el servicio

• Controlar y optimizar el acceso

• El Proxy implementa exactamente el mismo interfaz

- No es un decorador (no añade funcionalidad)

- No es una fachada (no simplifica)

Proxy

Tres tipos de proxy:

• Virtual Proxy

- Sustituye al objeto real mientras carga

- Optimización

• Protection Proxy

- Controla el acceso

- Seguridad

• Remote Proxy

- Reflejo de un recurso remoto

- Abstracción

Virtual Proxy

var Recurso = Class.extend({ init: function() { operacionMuyCostosa(); }, read: function() { ... }, write: function() { ... }});

Virtual Proxy

var Recurso = Class.extend({ init: function() { operacionMuyCostosa(); }, read: function() { ... }, write: function() { ... }});

var RecursoProxy = Recurso.extend({ init: function() { this.super_init = this._super; }, read: function() { this.super_init(); this._super(); }, write: function() { this.super_init(); this._super(); }});

Virtual Proxy

var Recurso = Class.extend({ init: function() { operacionMuyCostosa(); if (this.onLoad) this.onLoad(); }, write: function() { ... }});

var RecursoProxy = Recurso.extend({ init: function() { this.pending = []; this.loaded = false; this._super(); }, write: function() { if (loaded) { this._super(); } else { pending.push({op: 'write', params: arguments}); } }});

Remote Proxy

var User = Class.extend({ init: function() { /*...*/ }, getName: function() { return this.name; }});

User.find = function(id, cb) { $.get('/user/' + id, function(data) { var user = new User(data); cb(user); });};

User.find(12, function(user) { console.log(user.getName());});

Proxy

¿Cuándo usar un Proxy?

• Inicialización perezosa

• “Sustituto” que anote mientras el sujeto real arranca

• Cuando el sujeto real es remoto

• Cache!

Patrones de interacción

Patrones de interacción

• Pub/Sub u Observador

• Mediator

• Comandos y Cadena de Responsabilidad

• “Hydra”

Observador

En vez de preguntar, escucha lo que sucede

• Muy común en JavaScript (eventos)

• Reaccionar ante cambios de estado o sucesos

• Dos entidades

- Publicador

- Suscriptor(es)

Observador

var Input = Class.extend({ getValue: function() { return this.value; }});

var InputView = Class.extend({ init: function(input) { this.input = input; this.value = this.input.getValue(); setInterval(bind(this, this.onTimer), 100); }, onTimer: function() { var newValue = this.input.getValue(); if (newValue !== this.value) { this.value = newValue; console.log("CHANGED: " + this.value); } }});

Observador

InputInputViewcambios?

no..

InputInputViewcambios?

no..

InputInputViewcambios?

no..

InputInputViewcambios?

no..

0ms

100ms

200ms

300ms

Observador

InputInputViewavísame!

InputInputViewcambios!

0ms

T ms

Observador

var Input = Class.extend({ init: function() { this.observers = []; }, getValue: function() { return this.value; }, setValue: function(v) { this.value = v; this.publish(v); }, onChange: function(cb) { this.observers.push(cb); }, publish: function(cb) { this.observers.forEach(function(o) { o(cb); }); }});

Observador

var Input = Class.extend({ init: function() { this.observers = []; }, getValue: function() { return this.value; }, setValue: function(v) { this.value = v; this.publish(v); }, onChange: function(cb) { this.observers.push(cb); }, publish: function(cb) { this.observers.forEach(function(o) { o(cb); }); }});

Observador

var InputView = Class.extend({ init: function(input) { this.input = input; this.input.onChange(bind(this, this.update)); }, update: function(newValue) { console.log("CHANGED: " + newValue); }});

Observador

var input = new Input();

var observer1 = new InputView(input);var observer2 = new InputView(input);var observer3 = new InputView(input);

input.setValue("No te duermas!");

Intermedio: Observable

¡Podemos hacerlo mejor!

Input.mixin(Observable);

var InputView = Class.extend({ init: function(input) { this.input.subscribe('change', bind(this, this.update)); this.input.subscribe('invalid', bind(this, this.invalid)); }, update: function(newValue) { console.log("CHANGED: " + newValue); }, invalid: function(value) { console.log("El valor " + value + " es inválido!" ); }});

Intermedio: Observable

var Observable = { mixed: function(klass) { // ??? }, subscribe: function(event, callback) { // ??? }, unsubscribe: function(event, callback) { // ??? }, publish: function(event) { // ??? }};

Observador

¿Cuándo utilizar Observador?

• Se utiliza muy a menudo!

• Propagar cambios

• Reaccionar a sucesos

• Comunicación uno-a-muchos

Mediator

Un punto central de control del sistema

• Desacoplamiento a gran escala

• Sencillo y muy importante

• Dividir la lógica en dos niveles

- Componente

- Aplicación

Mediator

Mediator

x

Mediator

x

Aceptar

Mediator

x

Aceptar

Mediator

componente

componente

componente

componente

componente

Mediator

componente

componente componente

componente componente

mediador

Mediator

componente

componente componente

componente componente

mediador

Mediator

var Mediator = Class.extend({ init: function(fn) { this.connections = {}; if (fn) fn(this); }, on: function(msg, cb) { this.connections[msg] = cb; }, send: function(msg) { var args = [].slice.call(arguments, 1), action = this.connections[msg]; if (action) action.apply({}, args); }, addComponent: function(component) { component.setMediator(this); }});

Intermedio: Mediator

• Mediador en acción:

- tema2/mediador-1/index.html

Intermedio: Mediator

// Inicialización

$(function() {

var mediator = new Mediator(), button1 = new Button('pulsador', '#button-1'); mediator.addComponent(button1); mediator.on('pulsador:clicked', function() { alert("hola?"); }); });

Intermedio: Mediator

// Inicialización

$(function() {

var mediator = new Mediator(function(mediator) { var button1 = new Button('pulsador', '#button-1'); mediator.addComponent(button1); mediator.on('pulsador:clicked', function() { alert("hola?"); }); }); });

Intermedio: Mediator

• Mediador en acción:

- tema2/mediador-2/index.html

Intermedio: Mediator

mediator.on('ignore:on', function() { enabled = false;});

mediator.on('ignore:off', function() { enabled = true;});

mediator.on('inc:clicked', function() { if (enabled) { count.increment(); } display.update(count.getCurrentValue());});

mediator.on('dec:clicked', function() { if (enabled) { count.decrement(); } display.update(count.getCurrentValue());});

Intermedio: Mediator

mediator.on('ignore:on', function() { enabled = false;});

mediator.on('ignore:off', function() { enabled = true;});

mediator.on('inc:clicked', function() { if (enabled) { count.increment(); } display.update(count.getCurrentValue());});

mediator.on('dec:clicked', function() { if (enabled) { count.decrement(); } display.update(count.getCurrentValue());});

Mediator

¿Cuándo usar un Mediador?

• Muchos componentes interactuando

• La interacción se puede separar del componente

• Es decir: coordinar diferentes widgets de la aplicación

Mediator

¿Cuándo usar un Mediador?

• Muchos componentes interactuando

• La interacción se puede separar del componente

• Es decir: coordinar diferentes widgets de la aplicación

¡Cuidado!

๏ El mediador tiende a convertirse en un cajón de sastre

๏ Una Gran Función que Todo lo Hace!

Comandos

Separar la preparación de una acción y su ejecución

• Flujos de ejecución no convencionales

• Tres actores: cliente, invocador, receptor

- cliente: prepara o configura una acción

- invocador: controla la ejecución

- receptor: lleva a cabo la acción

• Generalizar comportamientos

Comandos

tema2/comandos-1/index.html

Comandos

var Command = Class.extend({ init: function(msg) { this.msg = msg; }, execute: function() { alert(this.msg); }});

var ActionList = Class.extend({ init: function(selector) { this.ul = $(selector); }, append: function(name, command) { var li = $("<li>") .append($('<a/>', {html:name, href:'#'})) .click(bind(command, command.execute)) .appendTo(this.ul); }});

var list = new ActionList('#menu');list.append('saludo', new Command("Hola!"));list.append('achis!', new Command("Salud!"));

Comandos

var Command = Class.extend({ init: function(msg) { this.msg = msg; }, execute: function() { alert(this.msg); }});

var ActionList = Class.extend({ init: function(selector) { this.ul = $(selector); }, append: function(name, command) { var li = $("<li>") .append($('<a/>', {html:name, href:'#'})) .click(bind(command, command.execute)) .appendTo(this.ul); }});

var list = new ActionList('#menu');list.append('saludo', new Command("Hola!"));list.append('achis!', new Command("Salud!"));

Comandos

var AlertCommand = Class.extend({ init: function(msg) { this.msg = msg; }, execute: function() { alert(this.msg); }});

var LogCommand = Class.extend({ init: function(msg) { this.msg = msg; }, execute: function() { console.log(this.msg); }});

Comandos

Encapsular las acciones y controlar su ejecución

• Una idea muy potente

• Deshacer! (estados reversibles)

• Historial

• Control de cambios

• Sincronización de estados

Comandos

Un ejemplo sencillo de estado reversible:

• tema2/comandos-2/index.html

var Movement = Class.extend({ init: function(amount, axis) { this.amount = amount; this.axis = axis; }, execute: function(mosca) { mosca.move(this.amount, this.axis); }, undo: function(mosca) { mosca.move(-this.amount, this.axis); }});

Comandos

¿Cuándo usar Comandos?

• Controlar la ejecución de acciones

• Separar la definición de la acción y su ejecución

• Es decir:

- Intervienen actores que pueden fallar (servidor, usuario)

- Menús, selectores y otros “contenedores de acciones”

- Sincronizar cambios simultáneos (control de versiones)

Cadena de Responsabilidad

Cada eslabón decide actuar o delegar en el próximo

• Elegir dinámicamente quien responde a una petición

• Dividir una operación compleja en varios pasos

• Desacoplar la complejidad del cliente

Cadena de Responsabilidad

var AlmacenVehiculos = Class.extend({ init: function(tipo) { this.tipo = tipo; this.vehiculos = []; }, guardar: function(vehiculo) { if (vehiculo.getTipo() == this.tipo) { this.vehiculos.push(vehiculo); } }});

var hangar = new AlmacenVehiculos('avion'), parking = new AlmacenVehiculos('coche'), puerto = new AlmacenVehiculos('barco');

Cadena de Responsabilidad

var Vehiculo = Class.extend({ init: function(tipo) { this.tipo = tipo; }, getTipo: function() { return this.tipo; }, guardar: function() { hangar.guardar(this); puerto.guardar(this); parking.guardar(this); }});

Cadena de Responsabilidad

var AlmacenVehiculos = Class.extend({ init: function(tipo, siguiente) { this.tipo = tipo; this.vehiculos = []; this.siguiente = siguiente; }, guardar: function(vehiculo) { if (vehiculo.getTipo() == this.tipo) { this.vehiculos.push(vehiculo); } else if (siguiente.next) { this.siguiente.guardar(vehiculo); } }});

Cadena de Responsabilidad

var hangar = new AlmacenVehiculos('avion'), parking = new AlmacenVehiculos('coche', hangar), puerto = new AlmacenVehiculos('barco', parking), almacenes = new AlmacenVehiculos('', puerto);

var Vehiculo = Class.extend({ init: function(tipo) { this.tipo = tipo; }, getTipo: function() { return this.tipo; }, guardar: function() { almacenes.guardar(this); }});

Cadena de Responsabilidad

¿Cuándo usar Cadenas de Reponsabilidad?

• Jerarquías de objetos

- Los eventos del navegador

• Diferentes fuentes para responder a una petición

- Ej: buscar por nombre, por fecha, por ciudad, ...

• El input puede afectar a varias entidades

- Ej: Colisiones

Hydra

Árbol de comportamientos

• No existe

• Organización al más alto nivel

• Mediador + Comandos + Cadena de Responsabilidad

• Extremadamente escalable!

• Para aplicaciones muy grandes

Hydra

Librería DOM

Componente

Mediador Global

Componente Componente

Hydra

Librería DOM

Mediador Global

Mediador Local

Componente

Componente

Componente

Mediador Local

Componente

Componente

Componente

Mediador Local

Componente

Componente

Componente

Hydra

Librería DOM

Mediador Global

Mediador Local

Componente

Ajax + DOM

Coordinación general

Coordinación local

Lógica del componente

Hydra

Librería DOM

Mediador Global

Mediador Local

Componente

Ajax + DOM

Coordinación general

Coordinación local

Lógica del componente

click en “Guardar”

Hydra

Librería DOM

Mediador Global

Mediador Local

Componente

Ajax + DOM

Coordinación general

Coordinación local

Lógica del componente

click en “Guardar”

Dame los datosdel formulario

Componente

Hydra

Librería DOM

Mediador Global

Mediador Local

Componente

Ajax + DOM

Coordinación general

Coordinación local

Lógica del componente

click en “Guardar”

Guardar nuevosdatos de usuario

Hydra

Librería DOM

Mediador Global

Mediador Local

Componente

Ajax + DOM

Coordinación general

Coordinación local

Lógica del componente

click en “Guardar”

Guardar nuevosdatos de usuario

Actualiza lalista decontactos

Mediador Local

Hydra

Librería DOM

Mediador Global

Mediador Local

Componente

Ajax + DOM

Coordinación general

Coordinación local

Lógica del componente

click en “Guardar”

Guardar nuevosdatos de usuario

Haz un POSTa /users

Hydra

Separación por capas de significado

• Cada mediador gestiona lo que hay a su nivel

• El significado de las acciones va cobrando sentido

• La lógica de los componentes se limita a hablar con su mediador

• La coordinación es “recursiva”

Hydra

¿Cuándo utilizar Hydra?

• Aplicaciones grandes!

• Widgets complicados que se beneficien de mediación

• En general, en cuanto un mediador engorde demasiado

Hydra

¿Cuándo utilizar Hydra?

• Aplicaciones grandes!

• Widgets complicados que se beneficien de mediación

• En general, en cuanto un mediador engorde demasiado

¡Cuidado!

๏ El alto desacoplamiento hace complicado leer el código

๏ El significado TOTAL de cada acción está diseminado

MVC: Backbone.js

made with love by Redradix (www.redradix.com)

El problema: acoplamiento

Las aplicaciones JS tienden a ser un caos

• La lógica del interfaz se mezcla con

• La lógica de control y validación de datos y con

• La lógica de comunicación con el servidor y con

• La lógica de reacción a eventos!

El problema: acoplamiento

El peor tipo de código espagueti!

$.ajax({ url: 'events/since', data: { timestamp: old_id }, dataType: 'json', type: 'GET', global: false, cache: false, success: function(newEvents) { if (newEvents.events && newEvents.events.length != 0) { if (prepend_events) { if (page <= '1') { if ($('div.new_events').length == 0) { $('#events').prepend('<div class="note new_events"><strong class="new_event_count">'+newEvents.events.length+'</strong> New Events Are Available Click here To View Them.</div>');

MVC

En los 70 se propuso una solución:

• Separar el código en 3 componentes:

- Modelo: datos y lógica de negocio

- Vista: presentación de los datos

- Controlador: gestión de interacciones

• Limitar su comunicación

• Muy popular desde hace mucho tiempo!

- Aplicaciones de escritorio

- Aplicaciones móviles

- Una interpretación peculiar en los frameworks web

MVC

Se ha extendido hace “poco” por JS

• Backbone.js

• Spine.js

• Batman.js

• Ember.js

• ...

MVC

M

V C

Usuario

Ve Usa

ManipulaActualiza

MVC

Vamos a aplicar el patrón MVC

• Entender su filosofía

• Comprender los mecanismos fundamentales

• Apreciar sus ventajas

• Ver sus limitaciones

• Estudiar diferentes soluciones

MVC

JS con todas nuestras utilidades:

• tema4/lib/prelude.js

- Class con herencia de propiedades de clase

- bind, curry, clone, merge

- Namespaces

- Observable

- Algunas utilidades extra

Modelo

Modelo

El Modelo se encarga de manejar los datos

• Representa una entidad

• Mecanismos de lectura y modificación

• Operaciones con los datos

• Validación

• Serialización

Modelo

Representar una entidad

var Usuario = Backbone.Model.extend({ /* ... */})

Modelo

Un modelo, en esencia, un conjunto de atributos

var u = new Usuario();

/* Crear o modificar un atributo */u.set({nombre: "Pepito"});

/* Leer el valor de un atributo */var nombre = u.get("nombre");console.log(nombre);

Modelo

Valores por defecto para los atributos: defaults

var Usuario = Backbone.Model.extend({ defaults: { nombre: "Anónimo", nacionalidad: "Español" }});

var u = new Usuario();console.log(u.get("nombre"));

u.set({nombre: "Pepito"});console.log(u.get("nombre"));

Modelo

Un modelo es un objeto, ¡Pero ten cuidado!

var Usuario = Backbone.Model.extend({ defaults: { nombre: "Anónimo", nacionalidad: "Español" }, saludar: function() { return "Hola, soy " + this.nombre; }});

var u = new Usuario();u.set({nombre: "Pepito"});console.log(u.saludar()); // "Hola, soy undefined"

Modelo

Los atributos del modelo se consultan con get/set

var Usuario = Backbone.Model.extend({ defaults: { nombre: "Anónimo", nacionalidad: "Español" }, saludar: function() { return "Hola, soy " + this.get("nombre"); }});

var u = new Usuario();u.set({nombre: "Pepito"});console.log(u.saludar()); // "Hola, soy Pepito"

Modelo

Serializar los atributos: .toJSON()

var Usuario = Backbone.Model.extend({ defaults: { nombre: "Anónimo", nacionalidad: "Español" }, saludar: function() { return "Hola, soy " + this.get("nombre"); }});

var u = new Usuario();u.set({nombre: "Pepito"});

var attrs = u.toJSON();console.log(attrs);

Modelo

Validez: método .validate(attrs)• El método no viene definido por defecto

• Nosotros lo creamos para validar nuestro modelo

• Se llama automáticamente desde save

• Se puede forzar al hacer .set()

Modelo

Validez: método .validate(attrs)• Si los valores de attrs no son válidos:

- Devuelve una descripción del error

- Puede ser cualquier cosa...

• Si son válidos:

- No se devuelve nada

Modelo

var Usuario = Backbone.Model.extend({ defaults: { nombre: "Anónimo", nacionalidad: "Español" }, validate: function (attrs) { if (/^\s*$/.test(attrs["nombre"])) { return "El nombre no puede quedar en blanco!"; } }});

var u = new Usuario();

u.set({nombre: "Pepito"}, {validate: true});console.log(u.get("nombre"));

u.set({nombre: " "}, {validate: true});console.log(u.get("nombre")); // "Pepito"

Modelo

Backbone.js es una librería agnóstica en muchos sentidos

• No impone templates

• No impone estructura

• No impone almacenamiento

Modelo

Backbone.js es una librería agnóstica en muchos sentidos

• No impone templates

• No impone estructura

• No impone almacenamiento

Excepto...

• Impone su propio sistema de herencia y clases

• Muy simple

• Muy limitado

Modelo

var Usuario = Backbone.Model.extend({ initialize: function () { // Constructor }});

var Ninja = Usuario.extend({ initialize: function () { Usuario.prototype.initialize.apply(this, arguments); // Subclase }});

Modelo

Parecida a la nuestra, pero:

• No se pueden crear objetos que no hereden de una clase base de Backbone (Model, View, Collection o Router)

• La manera de llamar al súper método es incómoda y viola el principio DRY

Modelo

Por suerte...

• Javascript es tremendamente flexible

• El modelo de Backbone es muy simple

• klass.js la hemos escrito nosotros y entendemos cómo funciona

Modelo

Podemos integrar ambas librerías fácilmente!

• “Envolviendo” las clases base de Backbone en clases de klass.js

• Así tenemos lo mejor de las dos

klass.js <-> backbone.js

var ProJS = (function (my) { var wrapBackboneClass = function(className) { var backboneWrapped = Backbone[className], F = function() {}, K = function() {}; F.prototype = backboneWrapped.prototype; K.prototype = new F(); _.extend(K, ProJS.Class, {constructor: backboneWrapped}); _.extend(K.prototype, ProJS.Class.prototype); K.prototype.init = function() { return backboneWrapped.apply(this, arguments); }; return K; };

my.Model = wrapBackboneClass('Model'); my.View = wrapBackboneClass('View'); my.Collection = wrapBackboneClass('Collection');

return my;}(ProJS || {}));

klass.js <-> backbone.js

Ahora podemos hacer:

var Usuario = ProJS.Model.extend({ init: function() { // constructor this._super(); }, defaults: { // etc... }});

Modelo

Ejercicio (uno fácil para empezar):

tema4/model-1/index.html

• Escribe un modelo Producto

- Id (incremento automático)

- Nombre (obligatorio)

- Categoría (obligatorio)

- País (España, Portugal o Francia)

- Precio (obligatorio, sin IVA)

• Escribe un decorador para obtener precios con IVA

Modelo

Fíjate en...

• Lo fácil que resulta sobreescribir la funcionalidad de un método con klass.js

• Si no tuviéramos klass.js, ¿Cómo habríamos hecho el decorador? ¿De quién tendría que haber heredado?

• Puedes pasar varios atributos a la vez a .set()

• Puedes pasar los atributos directamente al constructor

• Es una base bastante sólida para controlar los datos de nuestra aplicación!

Modelo

Los modelos generan eventos

• Cuando alguno de sus atributos cambia: change

• Cuando un atributo en concreto cambia: change:[attr]

• Cuando las validaciones fallan: invalid

• Al ser destruidos: destroy

• Al ser sincronizado con el servidor: sync

• Si surge algún error al guardar: error

Modelo

var p1 = new Producto();

p1.on("change", function (model, options) { console.log("El producto", model.get("nombre"), "ha cambiado!");});

p1.set({ nombre: "Jamón", categoria: "Comida", pais: "España", precio: 65});

Modelo

Ejercicio:

• Modifica el ejercicio anterior de modo que:

• Cuando una validación falla, se informe al usuario del error por la consola

Modelo

Otras operaciones útiles:

.unset(): elimina un atributo

.clear(): elimina todos los atributos

.previous(attr): durante un evento change, devuelve el valor anterior de un atributo

.has(attr): ¿Tiene el modelo el atributo attr?

.escape(attr): como .get(), pero escapando el HTML

Modelo

Persistencia

• La “gracia” de Backbone es que sabe como hablar con el servidor

• Pedir y guardar modelos automáticamente por AJAX

• Muy flexible

• Soporta también localStorage

Modelo

Para hablar con el servidor, el modelo necesita:

• urlRoot: la base con la que construir su URL

• id: el identificador del recurso

• parse (opcional): una función que interprete la respuesta del servidor

Modelo

Las operaciones fundamentales de persistencia:

.fetch()1. dado un id, construye una url del tipo [baseURL]/[id]

2. GET al servidor

3. pasa la respuesta a .parse()4. con el resultado llama a .set() para establecer los atributos

del modelo

Modelo

Ejercicio

tema4/model-2/index.html

Con una api AJAX tal que:

GET /products -> lista de {nombre: “”, id: #}

GET /products/:id -> detalles del producto :id

PUT /products/:id -> guarda cambios del producto :id

Haz:

- Construye un array con un modelo por cada producto del listado

- Modifica algún producto y guarda los cambios

- Escucha los eventos “change”y “sync” e informa por consola

Colecciones

Colección: conjunto ordenado de modelos

• Se identifica con un listado de recursos

- GET a URLs de tipo “índice” (/users, /products, etc...)

• Los modelos de una colección son del mismo tipo

Colecciones

• Objetivo similar al del un modelo, pero con conjuntos

- Manejar colecciones de datos

- Ordenar, añadir, eliminar entidades a la colección

- Consultar y guardar la colección en el servidor

- Serializar el conjunto de datos

Colecciones

var ListadoProductos = ProJS.Collection.extend({ model: Producto});

var listado = new ListadoProductos();

Colecciones

Operaciones fundamentales:

- add(model, {at: i}): añadir un modelo a la colección

- remove(model|id|cid): eleminar un modelo

- get(id|cid): acceder a un objeto de la colección por id

- at(idx): acceder a un objeto de la colección por índice

- length: número de elementos en la colección

- where(attrs): query de atributos

- push/pop, shift/unshift

Colecciones

var listado = new ListadoProductos();

listado.add(new Producto({nombre: "Uno"}));listado.add(new Producto({nombre: "Dos"}));listado.add(new Producto({nombre: "Tres"}));

console.log(listado.at(2).toJSON());

Colecciones

Para identificar a los modelos dentro de una colección, podemos usar:

• id- Generalmente otorgado por el servidor

- Se utiliza para construir la URL del modelo

- Universal dentro de la app

- Se corresponde, habitualmente, con el ID de la tabla en BBDD

• cid- Generado automáticamente por Backbone

- Válido solo dentro de la página

- Modelos no guardados o que no tienen que ver con BBDD

Colecciones

console.log(new Producto().cid); // c1console.log(new Producto().cid); // c2console.log(new Producto().cid); // c3

Colecciones

var p = new Producto({nombre: "Zapatos"}), cid = p.cid;

console.log(listado.get(cid)); // undefined

listado.add(p);console.log(listado.get(cid)); // p

var resultado = listado.where({nombre: "Zapatos"});console.log(resultado.toJSON()); // p

Colecciones

Una colección sabe como interpretar datos “crudos” (instancia automáticamente un modelo)

listado.add({ nombre: "Corbata", categoría: "Caballero", precio: 40});

listado.at(0).constructor === Producto; // true

Colecciones

Cargar datos iniciales en una colección: reset

var listado = new ListadoProductos();

listado.reset([ {nombre: "Uno", categoria: "A", precio: 2}, {nombre: "Dos", categoria: "B", precio: 1}, {nombre: "Tres", categoria: "C", precio: 8},]);

Colecciones

Las 28 funciones de underscore para manipular listas se pueden aplicar a colecciones

mapreducefindfiltermax/minsortshuffleetc...

Colecciones

Persistencia:

url: dirección para pedir la colección

- Los modelos de la colección usarán esta URL como urlRoot para construir sus URLs individuales

fetch: pide la colección al servidor

- GET a url, esperando un array de hashes (atributos)

set: refresca los datos de la colección

- Aproximadamente igual que hacer un fetch y mergear

Colecciones

Ejercicio:

tema4/collection-1/index.html

Crea una colección de Productos que lea de /products

Y tenga los métodos:

listado(): todos los productos

ordenaPorNombre(): filtro

precioMenorQue(p): filtro

borrarProducto(id): lo elimina de la BBDD

nuevoProducto(attrs): añade el producto a la colección y lo guarda en BBDD

Colecciones

Las colecciones también emiten eventos:

add(model, col): se ha añadido un nuevo modelo

remove(model, col): se ha eliminado un modelo

sort(col): se ha reordenado

Vista

Es una representación del modelo

• Asociada a una instancia de un modelo

• Generalmente utiliza un template

• Actualización automática

Vista

Templates

• “Plantillas” que mezclan HTML y código JS

• Backbone funciona con cualquier librería de templates

• Trae una preinstalada: _.template()

Vista

_.template(texto, datos)• texto: el texto de nuestra plantilla

• datos: un objeto con las variables que queramos utilizar al evaluar el template

Vista

En el texto de la plantilla:

<%= expresión %>Se sustituye por el resultado de evaluar la expresión

<% código %>Ejecuta el código javascript

Vista

var plantilla = "<h1> Hola! </h1>";console.log( _.template(plantilla, {}));

Vista

var plantilla = " <h1> \ <% console.log('Hola!') %> \ </h1>";

console.log( _.template(plantilla, {}));

Vista

var plantilla = " <h1> \ <%= 10 + 10 %> \ </h1>";

console.log( _.template(plantilla, {}));

Vista

var plantilla = " <h1> \ Bienvenido, <%= nombre %> \ </h1>";

console.log( _.template(plantilla, {nombre: "Ulises"}));

Vista

Anatomía de una vista

• Un modelo del que se extraen los datos

• Un template que se rendea con los datos del modelo

• Un nodo del DOM donde se inserta el template rendeado

• Un método .render() que se encarga de ejecutar este proceso

Vista

el Vista Modeloescuchaactualiza

Vista

En Backbone:

.model: el modelo asociado a la vista

.tagName: la etiqueta para generar el nodo del DOM

.attributes: los atributos para generar el nodo

.el: el nodo, ya creado

.$el: el nodo, envuelto con jQuery (o Zepto)

.$: un atajo a jquery pero con el scope fijado en .el

.render: el método que se encarga de rendear el template y actualizar el contenido de .el

Vista

var MiVista = ProJS.View.extend({ init: function(options) { this._super(options); }, tagName: "div", attributes: {"class": "box large"}, template: "<h1> <%= nombre %> </h1>", render: function() { var data = this.model.toJSON(); this.$el.html( _.template(this.template, data) ); return this; }});

var producto = new Producto({nombre: "Vino"}), miVista = new MiVista({model: producto}).render();

$("body").append(miVista.el);

Vista

Ejercicio: una vista sencilla

tema4/view-1/index.html

Crea una vista que utilice el template #producto-template para mostrar una instancia de Producto

Vista

Ejercicio: vistas y colecciones

tema4/view-2/index.html

Crea una colección que se inicialice con los datos de la ruta /products

Para cada uno de los modelos:

Instancia una vista VistaListado

Muéstrala por pantalla

Una pista: ¡las colecciones emiten eventos!

Vista

La vista debería “escuchar” al modelo

Cambios automáticos cuando el modelo cambia

El patrón habitual:

var VistaListado = ProJS.View.extend({ init: function (options) { this._super(options); this.model.on("change", bind(this, this.render)); }, /* ... */});

Y ahora, ¿qué?

Tenemos Modelo y Vista

• Todo el mundo está de acuerdo en estos dos puntos

• Datos + presentación

• Todavía falta algo...

MV*

MV*

• El papel del Controlador no está tan claro

• Gestionar la interacción?

• Gestionar los eventos de la vista?

• Gestionar las rutas de la página?

• Gestionar al modelo?

• ...

MV*

La visión tradicional:

Modelo Controlador Vista

MV*

En JavaScript...

M Controlador V

MV*

En JavaScript...

M Controlador V

TemplateProxy ¡Todo lo demás!

Controlador

El modelo “estándar” de Backbone.js

• Nadie lo dice abiertamente, pero:

• No hay Backbone.Controller

• La vista propiamente dicha es el template

• Pero Backbone.View gestiona la interacción...

• Es decir, hace de controlador

Controlador

Si no te he convencido...

¿Quién reacciona al input del usuario?

Backbone.View

¿Quién se encarga de actualizar los datos del modelo según ese input?

Backbone.View

¿Dónde se programa la lógica del interfaz de usuario?

Backbone.View

Controlador

Escuchar eventos en la vista:

var VistaProducto = ProJS.View.extend({ events: { "click a": "marcarComoActivo" }, init: function(options) { this._super(options); this.template = $("#template-producto").html(); this.model.on("change", bind(this, this.render)); },

// Event handlers marcarComoActivo: function() { this.model.set({active: true}); }

/* ... */});

Controlador

Ejercicio: cogiendo el feeling del controlador

tema4/controller-1/index.html

Haz que las vistas VistaListado:

- Escuchen los cambios del modelo y se auto-rendeen

- Escuchen el evento click en el <a> dentro de this.el y lo asocien a marcarComoActivo

- marcarComoActivo pone a true la propiedad “activo” del modelo

- Si la propiedad “activo” del modelo es true, añade la clase CSS “active” a this.el (que debería ser un <li>)

Controlador

El ejemplo anterior tiene problemas:

¿Cómo podemos decirle a los demás elementos que se desactiven?

¿Cómo podemos avisar al resto de la aplicación que se ha seleccionado un nuevo Producto?

MV*

¡Un Mediador!

• La mejor solución para coordinar componentes de una página

• Muy bajo acoplamiento:

- Modificar los componentes sin problemas

- Añadir o eliminar funcionalidad en la página

• MV* + Mediador = un patrón muy común y muy flexible

MV*

Un ejemplo:

tema4/controller-2/index.html

Fíjate como VistaListado simplemente notifica al mediador

Y el mediador es quien se encarga de orquestar los demás elementos

Las ventajas son:

Tenemos todo el flujo “a vista de pájaro” de la página en un solo sitio: el mediador

Ningún componente está acoplado a ningún otro, solo notifica a su mediador

MV*

Ejercicio: un poco de todo!

Modifica el ejemplo anterior para que al hacer click en un elemento de la barra lateral se muestre ese modelo con una vista VistaProducto (la tabla de los primeros ejercicios)

Testing

made with love by Redradix (www.redradix.com)

¿Qué es?

Comprobación automática del código

• Organizada por casos

• Cada caso comprueba un aspecto

• Comparando el resultado obtenido con el esperado

¿Para qué sirve?

¡Para garantizar que todo funciona!

• Que el nuevo código es correcto

• Que no se ha roto nada de lo anterior

• Que una refactorización no ha introducido bugs

¿En JavaScript?

Una práctica que va penetrando poco a poco

• Aunque sigue sin estar muy extendida

• Necesaria para aplicaciones complejas

• En general, una garantía de calidad

Testing

Hay muchos tipos de tests:

• Unitarios: comprueban un componente o una parte específica del código

• Integración: comprueban la interacción de componentes

• Aceptación: comprueban los requisitos del proyecto

• Regresión: comprueban la corrección de cambios

• etc...

Tests Unitarios

La idea de test unitario es muy simple:

• Dado un componente del sistema

• Para cada caso posible

• Comprobar que se comporta de la manera adecuada

Test Unitarios

var Contador = ProJS.Class.extend({ init: function() { this.i = 0; }, get: function() { return this.i; }, inc: function() { this.i++; }, dec: function() { this.i--; }, reset: function() { this.i = 0; }});

Test Unitarios

¿Cómo podríamos comprobar, programáticamente, que Contador funciona bien?

Haciendo algo así:

➡ tema5/unitarios-1/index.html

Test Unitarios

Es bastante tedioso!

• Mucha repetición de código similar

• Se puede abstraer bastante

Test Unitarios

Segundo intento

➡ tema5/unitarios-2/index.html

Test Unitarios

var ContadorTests = Test.extend({ casos: { debe_empezar_a_cero: function(contador) { var i = contador.get(); this.assertEqual(i, 0, "Empieza a %1".format(i)); },

// ... }});

Jasmine

Estupenda librería de testing

• Al estilo rspec

• Sencilla

• Potente

• http://pivotal.github.com/jasmine/

Jasmine

¿Qué pinta tiene?

• tema5/jasmine-1/index.html

describe("Conjunto de tests", function() { it("debería ser un caso válido", function() { expect(true).toBe(true); }); it("debería ser un caso con error", function() { expect(true).toBe(false); });});

Jasmine

Test del contador con Jasmine

• tema5/jasmine-2/index.html

Jasmine

Test asíncronos

• ¿Cómo testearías que esta función llama al cb con true?

• tema5/jasmine-3/index.html

function asyncFn(cb) { setTimeout(function() { cb(true); }, 250);}

Jasmine

describe("Test asíncrono", function() { it("debería llamar al callback con true", function() { var result, callback = function(response) { result = response; }; runs(function() { asyncFn(callback); }); waitsFor(function() { return result == true; }, 300); runs(function() { expect(result).toBe(true); }); });});

Intermedio: Jasmine

¡Testea alguna de las funciones que hemos visto!

• La que te parezca más confusa

• Documentación y “matchers” de Jasmine en

➡ http://pivotal.github.com/jasmine/

Jasmine

Jasmine en la consola:

• Cambiar a ConsoleReporter

• Y un poco de magia funcional...

➡ tema5/consola-1/index.html

Jasmine

var lazyPrint = (function() { var buffer = "", print = function() { console.log(buffer); buffer = ""; }; print = debounce(print, 300); return function(msg) { buffer += msg; print(); };}());

Jasmine

¿Para qué sirve Jasmine en la consola?

• Dejar la página libre

• Poder cargar nuestro propio HTML

•¡Testear interacciones e interfaces!

Test de Integración (interfaz)

Comprobar que el UI funciona correctamente

• Simular la interacción del usuario disparando eventos DOM

• Observar el estado del programa inspeccionando el interfaz

• Asegurar la correcta integración de los componentes de la página

Jasmine

El resultado:

• tema5/integration/index.html

• Queremos testear que el intefaz funciona bien

• “Inc” incrementa el contador y el display

• “Dec” decrementa el contador y el display

• “Reset” lo pone a 0

• Salida por consola...

- Podríamos ver esta salida en algún emulador de DOM de node.js

- O hacer un reporter que se comunique con el servidor de integración continua

Spam Mode: ON

Al escribir test JS acaba surgiendo un problema:

• ¡Los datos!

• ¿De dónde saco datos válidos para testear?

• ¿Del servidor?

- No es fácil de conseguir modificar/resetear un set de datos cada vez que ejecuto un test

- Dependencia del backend

• Lo ideal sería:

- Factorías de datos (estilo FactoryGirl)

- Simular la interacción con el servidor de forma inocua

Solipsist.js

Solipsist.js es una librería auxiliar para testear

➡ https://github.com/WeRelax/solipsist-js

• Tests JS aislados

• Factorías

• Mocking de peticiones AJAX

• Otro uso: programar el frontend independiente del backend