Curso Javascript profesionales

1,402 views

Published on

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

Published in: Technology
0 Comments
4 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
1,402
On SlideShare
0
From Embeds
0
Number of Embeds
75
Actions
Shares
0
Downloads
89
Comments
0
Likes
4
Embeds 0
No embeds

No notes for slide

Curso Javascript profesionales

  1. 1. Javascript para Profesionales made with love by Redradix (www.redradix.com)
  2. 2. Fundamentos
  3. 3. Objetos ! ! Conjunto de propiedades propias + heredadas de otro objeto (prototipos) ¡Qué no cunda el pánico!
  4. 4. Objetos ! Dinámicos ! Set de strings var obj = {}; obj.nuevaPropiedad = 1; delete obj.nuevaPropiedad; var strset = { hola: true, adios: true }; "hola" in strset;
  5. 5. Objetos ! Referencias var p1 = {x: 1}, p2 = p1; p1 === p2; // true p1.x = 5; p2.x; // 5 ! ! Todo son objetos excepto: strings, números, booleans, null o undefined Strings, números y booleans se comportan como objetos inmutables
  6. 6. 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();
  7. 7. Objetos ! Literales { un: "objeto", literal: true - clase: Object - sencillos y ligeros ! Construidos - clase: prototype - constructor }; new NombreDeUnaFuncion();
  8. 8. 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.
  9. 9. Clases Si esto es un “punto” var point = {x: 0, y: 0}; Y esto es otro “punto” var point2 = {x: 5, y: 5}; ¿Qué es esto? var what = {x: 10, y: 10}; ¿Y esto? var isit = {x: -10, y: -20};
  10. 10. Mensajes • Teniendo: var obj = { nombre: "Pepito", saludo: function () { return "Hola, Mundo!"; } }; • ¿Que significa esto? obj.nombre;
  11. 11. Mensajes • Teniendo: var obj = { nombre: "Pepito", saludo: function () { return "Hola, Mundo!"; } }; • ¿Y esto? obj.saludo;
  12. 12. Mensajes • Teniendo: var obj = { nombre: "Pepito", saludo: function () { return "Hola, Mundo!"; } }; • ¿Y esto otro? (¡cuidado!) obj[“saludo”]();
  13. 13. Mensajes • Teniendo: var obj = { nombre: "Pepito", saludo: function () { return "Hola, Mundo!"; } }; • ¿Es lo mismo? var fn = obj["saludo"]; fn();
  14. 14. Mensajes • Teniendo: var obj = { nombre: "Pepito", saludo: function () { return "Hola, Mundo!"; } }; • ¡NO es no mismo! var fn = obj["saludo"]; fn();
  15. 15. Mensajes • Una función se puede ejecutar de 4 maneras: - Invocando directamente la función (function() { alert("Hey!"); })(); - Enviando un mensaje a un objeto (método) objeto.metodo(); - Como constructor new MiConstructor(); - Indirectamente, a través de call(...) fn.call({}, "param"); y apply(...)
  16. 16. Mensajes - Invocando directamente la función (function() { alert("Hey!"); })(); - Enviando un mensaje a un objeto (método) objeto.metodo();
  17. 17. Mensajes Un mensaje se envía a un receptor var obj = {}; obj.toString(); // [object Object] "hola don Pepito".toUpperCase();
  18. 18. Mensajes Un mensaje se envía a un receptor var obj = {}; obj.toString(); // [object Object] "hola don Pepito".toUpperCase();
  19. 19. Mensajes La sintaxis es engañosa var obj = { coleccion: ["uno", "dos", "tres"], metodo: function() { return "Hola, Mundo!"; } }; obj.coleccion[1]; vs. obj.metodo();
  20. 20. Mensajes var fn = obj.metodo; fn(); obj.metodo();
  21. 21. Mensajes var fn = obj.metodo; fn(); - Accede a la propiedad “metodo” de obj - Supongo que es una función y la invoco obj.metodo();
  22. 22. Mensajes var fn = obj.metodo; fn(); obj.metodo(); - Accede a la propiedad - Envía el mensaje “metodo” a - Supongo que es una función - Si existe, obj se encarga de “metodo” de obj y la invoco obj ejecutar la función
  23. 23. Mensajes var fn = obj.metodo; fn(); obj.metodo(); - Accede a la propiedad - Envía el mensaje “metodo” a - Supongo que es una función - Si existe, obj se encarga de - NO HAY RECEPTOR - obj ES EL RECEPTOR “metodo” de obj y la invoco obj ejecutar la función
  24. 24. Mensajes Un error típico: $("#elemento").click(objeto.clickHandler);
  25. 25. Mensajes Un error típico: $("#elemento").click(objeto.clickHandler); • Lo que se intenta decir: - “Al hacer click sobre #elemento, envía el mensaje clickHandler a objeto”
  26. 26. Mensajes Un error típico: $("#elemento").click(objeto.clickHandler); • Lo que se intenta decir: - “Al hacer click sobre #elemento, envía el mensaje clickHandler a objeto” • Lo que se dice en realidad: - “Accede al valor de la propiedad clickHandler de objeto y ejecútalo al hacer click sobre #elemento”
  27. 27. El receptor: ... ¿Por qué tanto lío con el receptor del mensaje?
  28. 28. 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
  29. 29. El receptor: this this = “el receptor de este mensaje” var nombre = "Sonia"; var obj = { nombre: "Pepito", saludo: function() { alert("hola " + this.nombre); } } obj.saludo();
  30. 30. 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
  31. 31. 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();
  32. 32. 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();
  33. 33. El receptor: this Semánticamente, es como un parámetro oculto function ([this]) { alert("hola " + this.nombre); } que el receptor se encargara de proveer obj.saludo(); => saludo([obj]);
  34. 34. 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();
  35. 35. 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]);
  36. 36. El receptor: this Semánticamente, es como un parámetro oculto var nombre = "Sonia"; var obj = { nombre: "Pepito", saludo: function([this]) { var saludo_fn = function([this]) { alert("hola " + this.nombre); }; saludo_fn([objeto global]); } }; obj.saludo([obj]);
  37. 37. 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)
  38. 38. El receptor: this Otro error común: var obj = { clicks: 0, init: function() { $("#element").click(function() { this.clicks += 1; }); } }; obj.init();
  39. 39. El receptor: this Otro error común: var obj = { clicks: 0, init: function([this]) { $("#element").click(function([this]) { this.clicks += 1; }); } }; obj.init([obj]);
  40. 40. 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();
  41. 41. Repaso: Mensajes • Una función se puede ejecutar de 4 maneras: - Invocando directamente la función (function() { alert("Hey!"); })(); - Enviando un mensaje a un objeto (método) objeto.metodo(); - Como constructor new MiConstructor(); - Indirectamente, a través de call(...) fn.call({}, "param"); y apply(...)
  42. 42. Repaso: Mensajes Una función se puede ejecutar de 4 maneras: Invocando directamente la función Enviando un mensaje a un objeto (método) Como constructor - Indirectamente, a través de call(...) fn.call({}, "param"); y apply(...)
  43. 43. El receptor: this • Las funciones son objetos • Se pueden manipular como cualquier otro objeto - Asignar valores a propiedades - Pasar como parámetros a otras funciones - Ser el valor de retorno - Guardarse en variables u otros objetos • Tienen métodos var fn = function() { alert("Hey!"); }; fn.toString();
  44. 44. El receptor: this • Dos métodos permiten manipular el receptor (contexto): - fn.call(context [, arg1 [, arg2 [...]]]) var a = [1,2,3]; Array.prototype.slice.call(a, 1, 2); // [2] - fn.apply(context, arglist) var a = [1,2,3]; Array.prototype.slice.apply(a, [1, 2]); // [2]
  45. 45. El receptor: this var nombre = "Objeto Global"; function saluda() { alert("Hola! Soy " + this.nombre); } var alicia = { nombre: "Alicia" }; saluda(); saluda.call(alicia);
  46. 46. 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);
  47. 47. arguments • Se comporta (más o menos) como Array... function echoArgs() { alert(arguments[0]); // 1 } echoArgs(1, 2, 3, 4); • ...pero NO del todo function echoArgs() { return arguments.slice(0, 1); // Error! } echoArgs(1, 2, 3, 4);
  48. 48. arguments • Un truco: function echoArgs() { var slice = Array.prototype.slice; return slice.call(arguments, 0, 1); } echoArgs(1, 2, 3, 4); // [1]
  49. 49. arguments ¡Cuidado, se comporta como parámetro oculto! function exterior() { var interior = function() { alert(arguments.length); }; interior(); } exterior("a", "b", "c");
  50. 50. intermedio: this y arguments ¿Qué hace esta función? function misterio(ctx, fn) { return function() { return fn.apply(ctx, arguments); } }
  51. 51. Intermedio: this y arguments function misterio(ctx, fn) { return function() { return fn.apply(ctx, arguments); } } var algo = misterio(); typeof algo; // ???
  52. 52. Intermedio: this y arguments function misterio(ctx, fn) { return function() { return fn.apply(ctx, arguments); } } var algo = misterio(); algo(); // ???
  53. 53. Intermedio: this y arguments function misterio(ctx, fn) { return function() { return fn.apply(ctx, arguments); } } var algo = misterio({}, function() { return this; }); typeof algo(); // ???
  54. 54. 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(); // ???
  55. 55. 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(); // ???
  56. 56. 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(); /// ???
  57. 57. Intermedio: this y arguments function misterio(ctx, fn) { return function() { return fn.apply(ctx, arguments); } } var obj = { nombre: "Bárbara" }; var algo = misterio(obj, function (saludo) { return saludo + " " + this.nombre; }); algo("Hola, "); /// ???
  58. 58. 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, "); /// ???
  59. 59. Intermedio: this y arguments • bind: fija una función a un contexto function bind(ctx, fn) { return function() { return fn.apply(ctx, arguments); } }
  60. 60. Intermedio: this y arguments Volviendo al problema: var obj = { clicks: 0, init: function() { $("#element").click(function() { // MAL this.clicks += 1; }); } }; obj.init();
  61. 61. Intermedio: this y arguments Apaño: var obj = { clicks: 0, init: function() { var that = this; $("#element").click(function() { that.clicks += 1; }); } }; obj.init();
  62. 62. 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();
  63. 63. 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(); }); } };
  64. 64. Intermedio: this y arguments • bind al rescate var obj = { clicks: 0, incClicks: function() { this.clicks += 1; }, init: function() { $("#element").click( bind(this, this.incClicks) ); } };
  65. 65. 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)); }; }
  66. 66. 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(); // ???
  67. 67. intermedio: this y arguments function enigma(fn) { var slice = Array.prototype.slice, args = slice.call(arguments, 1); return function() { var newargs = slice.call(arguments); return fn.apply(this, args.concat(newargs)); }; } var cosa = enigma(); typeof cosa(); // ???
  68. 68. intermedio: this y arguments function enigma(fn) { var slice = Array.prototype.slice, args = slice.call(arguments, 1); return function() { var newargs = slice.call(arguments); return fn.apply(this, args.concat(newargs)); }; } var cosa = enigma(function() { return "Hola!"; }); cosa(); // ???
  69. 69. intermedio: this y arguments function enigma(fn) { var slice = Array.prototype.slice, args = slice.call(arguments, 1); return function() { var newargs = slice.call(arguments); return fn.apply(this, args.concat(newargs)); }; } function saluda(nombre) { return "Hola, " + nombre + "!"; } var cosa = enigma(saluda); cosa("Mundo"); // ???
  70. 70. intermedio: this y arguments function enigma(fn) { var slice = Array.prototype.slice, args = slice.call(arguments, 1); return function() { var newargs = slice.call(arguments); return fn.apply(this, args.concat(newargs)); }; } function saluda(nombre) { return "Hola, " + nombre + "!"; } var cosa = enigma(saluda, "Mundo"); cosa(); // ???
  71. 71. intermedio: this y arguments function enigma(fn) { var slice = Array.prototype.slice, args = slice.call(arguments, 1); return function() { var newargs = slice.call(arguments); return fn.apply(this, args.concat(newargs)); }; } function saluda(saludo, nombre) { return saludo + ", " + nombre + "!"; } var cosa = enigma(saluda, "Hola", "Mundo"); cosa(); // ???
  72. 72. intermedio: this y arguments function enigma(fn) { var slice = Array.prototype.slice, args = slice.call(arguments, 1); return function() { var newargs = slice.call(arguments); return fn.apply(this, args.concat(newargs)); }; } function saluda(saludo, nombre) { return saludo + ", " + nombre + "!"; } var cosa = enigma(saluda, "Hola"); cosa("Mundo"); // ??? cosa("Don Pepito"); // ???
  73. 73. intermedio: this y arguments function enigma(fn) { var slice = Array.prototype.slice, args = slice.call(arguments, 1); return function() { var newargs = slice.call(arguments); return fn.apply(this, args.concat(newargs)); }; } 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); // ???
  74. 74. 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)); }; }
  75. 75. Intermedio: rizar el rizo var unObj = { nombre: "Manuel", edad: 32 }; function function function function getNombre() { return this.nombre; } setNombre(nombre) { this.nombre = nombre; } getEdad() { return this.edad; } setEdad(edad) { this.edad = edad; } var bindToUnObj = curry(bind, unObj), getUnObjNombre = bindToUnObj(getNombre), setUnObjNombre = bindToUnObj(setNombre); setUnObjNombre("Pepito"); getUnObjNombre(); // ???
  76. 76. 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(); // ???
  77. 77. Prototipos
  78. 78. Prototipos • ¿Por qué tan mala fama? • ¡Es un mecanismo muy sencillo! • Distinto a otros lenguajes
  79. 79. Prototipos Un objeto obj: var obj = {uno: 1, dos: 2}; qué pasa si hacemos: obj.uno; // 1
  80. 80. Prototipos obj var obj = {uno: 1, dos: 2}; uno 1 dos 2 uno 1 dos 2 obj obj.uno; // 1
  81. 81. Prototipos obj var obj = {uno: 1, dos: 2}; uno 1 dos 2 Si hacemos: obj.tres; // undefined obj uno 1 dos 2 Not found! undefined
  82. 82. Prototipos obj var obj = {uno: 1, dos: 2}; uno 1 dos 2 ¿De dónde sale? obj.toString(); // '[object Object]' obj uno 1 dos 2 Not found! undefined ¿?
  83. 83. Prototipos obj.toString(); // '[object Object]' obj uno 1 dos 2 prototype Object Object toString function valueOf function ... Not found! undefined
  84. 84. Prototipos Teniendo: a Object b uno 1 tres 3 toString function dos 2 cuatro 4 valueOf function prototype b prototype Object ... Not found! undefine d
  85. 85. Prototipos a.uno; // a 1 Object b uno 1 tres 3 toString function dos 2 cuatro 4 valueOf function prototype b prototype Object ... Not found! undefine d
  86. 86. Prototipos a.cuatro; // a 4 Object b uno 1 tres 3 toString function dos 2 cuatro 4 valueOf function prototype b prototype Object ... Not found! undefine d
  87. 87. Prototipos a.toString; // a [object Object] Object b uno 1 tres 3 toString function dos 2 cuatro 4 valueOf function prototype b prototype Object ... Not found! undefine d
  88. 88. Prototipos a.noExiste; // a undefined Object b uno 1 tres 3 toString function dos 2 cuatro 4 valueOf function prototype b prototype Object ... Not found! undefine d
  89. 89. 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!
  90. 90. Repaso: Mensajes • Una función se puede ejecutar de 4 maneras: - Invocando directamente la función (function() { alert("Hey!"); })(); - Enviando un mensaje a un objeto (método) objeto.metodo(); - Como constructor new MiConstructor(); - Indirectamente, a través de call(...) fn.call({}, "param"); y apply(...)
  91. 91. 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();
  92. 92. Constructores • Funciones • Invocación precedida por new • Su contexto es un objeto recién generado • return implícito • La única manera de manipular prototipos
  93. 93. 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"
  94. 94. Constructores function Constructor(param) { // this tiene otro significado! this.propiedad = "una propiedad!"; this.cena = param; } var instancia = new Constructor("Pollo asado"); instancia.propiedad; // una propiedad! instancia.cena; // "Pollo asado"
  95. 95. 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
  96. 96. Constructores var b = { uno: 1, dos: 2 }; function A() { this.tres = 3; this.cuatro = 4; } A.prototype = b; var instancia = new A(); instancia.tres; // 3 instancia.uno; // 1
  97. 97. Constructores var b = { uno: 1, dos: 2 }; instancia A.prototype = b; var instancia = new A(); instancia.tres; // 3 instancia.uno; // 1 1 dos 2 proto function A() { this.tres = 3; this.cuatro = 4; } uno b tres 3 cuatro 4 proto Object b
  98. 98. Constructores .hasOwnProperty(name) • Distinguir las propiedades heredadas de las propias • true solo si la propiedad es del objeto instancia.hasOwnProperty("tres"); // true instancia.hasOwnProperty("uno"); // false
  99. 99. 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; // ???
  100. 100. 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; // ???
  101. 101. Constructores ¿Qué pasa aquí? var comun = { empresa: "ACME" }; function Empleado(nombre) { this.nombre = nombre; } Empleado.prototype = comun; var pepe = new Empleado("Pepe"); comun.empresa = "Googlezon"; var antonio = new Empleado("Antonio"); pepe.empresa; // ???
  102. 102. Constructores comun empresa “ACME” proto Object pepe nombre “Pepe” proto comun var pepe = new Empleado("Pepe");
  103. 103. Constructores comun empresa “Googlezone” proto Object pepe nombre “Pepe” proto comun comun.empresa = "Googlezon";
  104. 104. Constructores comun empresa “Googlezone” proto Object pepe antonio nombre “Pepe” nombre “Antonio” proto comun proto comun var antonio = new Empleado("Antonio");
  105. 105. Constructores comun empresa “Googlezone” proto Object pepe antonio nombre “Pepe” nombre “Antonio” proto comun proto comun pepe.empresa;
  106. 106. 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)!
  107. 107. Intermedio: constructores ¿Cómo hacer que C herede de B que hereda de A?
  108. 108. Intermedio: constructores ¿Cómo hacer que C herede de B que hereda de A? C A B uno 1 dos 2 tres 3 proto B proto A proto Object var instancia = new C(); instancia.tres; // 3
  109. 109. Intermedio: constructores function C() { this.uno = 1; } var instancia = new C(); instancia.tres;
  110. 110. Intermedio: constructores var B = {dos: 2}; function C() { this.uno = 1; } C.prototype = B; var instancia = new C(); instancia.tres;
  111. 111. 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;
  112. 112. Intermedio: constructores var A = {tres: 3}; function B() { this.dos = 2; } B.prototype = A; function C() { this.uno = 1; } C.prototype = B; var instancia = new C(); instancia.dos; // !!!
  113. 113. 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; // ???
  114. 114. 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;
  115. 115. 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;
  116. 116. 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!
  117. 117. Mecanismos de herencia
  118. 118. Mecanismos de herencia • Herencia clásica • Herencia de prototipos • Mixins (módulos) • Extra: herencia funcional
  119. 119. Herencia clásica ¿Qué significa? • Clases! • El tipo de herencia más común en otros lenguajes • Encapsulado y visibilidad
  120. 120. Herencia clásica Vamos a empezar por un constructor: function MiClase() { // ??? } var instancia = new MiClase();
  121. 121. Herencia clásica Las propiedades (públicas) function MiClase() { this.unaPropiedad = "valor"; this.otraPropiedad = "otro valor"; } var instancia = new MiClase();
  122. 122. Herencia clásica ¿Métodos?
  123. 123. 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();
  124. 124. 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();
  125. 125. 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); };
  126. 126. Herencia clásica ¿Cómo se pueden crear métodos “de clase”? MiClase.metodoEstatico("hola!");
  127. 127. Herencia clásica ¿Cómo se pueden crear métodos “de clase”? MiClase.metodoEstatico("hola!"); ¡Los constructores son objetos! MiClase.metodoEstatico = function(cadena) { console.log("metodoEstatico:", cadena); }
  128. 128. Herencia clásica: Cuidado! Este esquema tiene varios problemas:
  129. 129. Herencia clásica: Cuidado! Este esquema tiene varios problemas: • ¡No es fácil invocar al super constructor!
  130. 130. 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; }
  131. 131. 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; }
  132. 132. 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; }
  133. 133. 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(); crea glob. especie! Perro.prototype.describir = function() { return this.getEspecie() + ", de raza " + this.raza; }
  134. 134. 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; }
  135. 135. 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(); ERROR! Perro.prototype.describir = function() { return this.getEspecie() + ", de raza " + this.raza; }
  136. 136. Herencia clásica: Cuidado! 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; }
  137. 137. Herencia clásica: Cuidado! Este esquema tiene varios problemas: • ¡No es fácil invocar al super constructor! • No es fácil encapsular
  138. 138. 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();
  139. 139. 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!
  140. 140. Herencia clásica: Cuidado! function Superclase() { operacionMuyCostosa(); alert(“Oh, no!”); } function MiClase() { // ... } MiClase.prototype = new Superclase();
  141. 141. Herencia clásica? ¿Qué se puede hacer?
  142. 142. Herencia clásica? Hay dos enfoques:
  143. 143. Herencia clásica? Hay dos enfoques: • El simple
  144. 144. 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; }
  145. 145. 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;
  146. 146. El “simple” function Animal() { } Animal.prototype.mover = function() { console.log("El animal se mueve..."); }; Animal.prototype.comer = function() { console.log("¡Ñam!"); }; function Perro(raza) { this.superclass.call(this); } inherits(Perro, Animal); Perro.prototype.comer = function() { console.log("El perro va a por su plato..."); this.superclass.prototype.comer.call(this); }; var p = new Perro("terrier"); p.mover(); p.comer(); p instanceof Perro; OMG!
  147. 147. 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
  148. 148. 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; };
  149. 149. El “simple” Caso práctico: CoffeeScript var __hasProp = {}.hasOwnProperty, __extends = function (child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
  150. 150. 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);
  151. 151. Herencia clásica? Hay dos enfoques: • El simple • El cómodo
  152. 152. 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!"); } });
  153. 153. El cómodo Más complejo, pero merece la pena var Persona = Class.extend({ init: function(nombre) { console.log("Bienvenido,", nombre); } }); var Ninja = Persona.extend({ init: function(){ this._super("ninja"); } esgrimirEspada: function(){ console.log("En guardia!"); } });
  154. 154. Intermedio: klass.js ¡Rellena los huecos! var Class = function(){}; Class.extend = function(prop) { var _super = this.prototype; // ... return Klass; };
  155. 155. 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!
  156. 156. 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”
  157. 157. Herencia de prototipos Vamos a cambiar de marcha...
  158. 158. Herencia de prototipos Vamos a cambiar de marcha... ¡Ahora, sin clases!
  159. 159. Herencia de prototipos Vamos a cambiar de marcha... ¡Ahora, sin clases! ¿¿Cómo puede haber POO sin clases??
  160. 160. 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!
  161. 161. Herencia de prototipos Herencia clásica: categorías -Definir “Persona” -crear instancias de persona según la definición -Para hacer más concreto, ¡redefine! 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!
  162. 162. Herencia de prototipos var benito = { nombre: "Benito", edad: 36, profesión: "jardinero", saludar: function() { alert("Buen día!"); }, envejecer: function() { this.edad += 1; } };
  163. 163. 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!
  164. 164. 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!
  165. 165. Herencia de prototipos También se puede generalizar var Animal = { vivo: true, comer: function() { console.log("Ñam, ñam"); } }; var Perro = clone(Animal); Perro.especie = "perro"; var Dogo = clone(Perro); Dogo.raza = "dogo"; var toby = clone(Dogo); toby.nombre = "Toby";
  166. 166. Herencia de prototipos ¡Así de simple! function clone(obj) { function F(){} F.prototype = obj; return new F(); }
  167. 167. 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
  168. 168. 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!
  169. 169. Herencia de prototipos ¿Cuál uso?
  170. 170. Herencia de prototipos ¿Cuál uso? ¡Las dos!
  171. 171. Intermedio: prototipos ¿Qué significa? objeto.propiedad;
  172. 172. Intermedio: prototipos ¿Qué significa? objeto.propiedad; “Accede a la propiedad propiedad del objeto objeto”
  173. 173. Intermedio: prototipos ¿Qué significa? objeto.propiedad; “Accede a la propiedad propiedad del objeto objeto. Si no la encuentras, sigue buscando por la cadena de prototipos.”
  174. 174. Intermedio: prototipos ¿Qué significa? objeto.propiedad = 1;
  175. 175. Intermedio: prototipos ¿Qué significa? objeto.propiedad = 1; “Guarda el valor 1 en la propiedad propiedad del objeto objeto.”
  176. 176. Intermedio: prototipos ¿Qué significa? objeto.propiedad = 1; “Guarda el valor 1 en la propiedad propiedad del objeto objeto. Si no la encuentras, créala!”
  177. 177. Intermedio: prototipos pluma plomo proto peso 1 proto pluma Object var pluma = { peso: 1 }; var plomo = clone(pluma);
  178. 178. Intermedio: prototipos pluma plomo proto peso 1 proto pluma Object plomo.peso; // 1
  179. 179. Intermedio: prototipos pluma plomo proto pluma peso 1 peso 100 proto Object plomo.peso = 100;
  180. 180. 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.
  181. 181. 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;
  182. 182. 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;
  183. 183. ¡Se acabaron los prototipos!
  184. 184. Clausuras Sólo una idea importante más: ámbitos
  185. 185. Clausuras • Una idea sencilla, pero difícil de explicar • Están por todas partes • Consecuencia natural del lenguaje • ¡Muy útiles!
  186. 186. Clausuras function clausurator() { var a = 1; return function() { return a; }; }
  187. 187. Clausuras function clausurator() { var a = 1; return function() { return a; }; } var fn = clausurator(); typeof fn;
  188. 188. Clausuras function clausurator() { var a = 1; return function() { return a; }; } var fn = clausurator(); fn(); // ???
  189. 189. Clausuras function clausurator() { var a = 1; return function() { return a; }; } var fn = clausurator(); fn(); fn = function() { return a; }
  190. 190. Clausuras function clausurator() { var a = 1; return function() { return a; }; } var fn = clausurator(); fn(); fn = function() { return a; }
  191. 191. Clausuras function clausurator() { var a = 1; return function() { return a; }; } var fn = clausurator(); fn(); fn = function() { return a; }
  192. 192. Clausuras function clausurator() { var a = 1; return function() { return a; }; } var fn = clausurator(); fn(); fn = function() { return a; } a = 1;
  193. 193. Clausuras Otro caso: function makeContador() { var i = 0; return function() { return i++; } } var contador1 = makeContador(); contador1(); // ??? contador1(); // ??? var contador2 = makeContador(); contador2(); // ???
  194. 194. Clausuras Otro caso: function makeContador() { var i = 0; return function() { return i++; } } var contador1 = makeContador(); contador1(); // 0 contador1(); // 1 var contador2 = makeContador(); contador2(); // 0
  195. 195. Clausuras function makeContador() { var i = 0; return function() { return i++; } } var contador1 = makeContador(); contador1(); // 0 contador1(); // 1 var contador2 = makeContador(); contador2(); // 0 contador1 = function() { return i++; } i = 0;
  196. 196. Clausuras function makeContador() { var i = 0; return function() { return i++; } } var contador1 = makeContador(); contador1(); // 0 contador1(); // 1 var contador2 = makeContador(); contador2(); // 0 contador1 = function() { return i++; } i = 1;
  197. 197. Clausuras function makeContador() { var i = 0; return function() { return i++; } } var contador1 = makeContador(); contador1(); // 0 contador1(); // 1 contador1 = function() { return i++; } i = 1; contador2 = function() { return i++; } var contador2 = makeContador(); contador2(); // 0 i = 0;
  198. 198. 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(); // ???
  199. 199. Clausuras • bind: fija una función a un contexto function bind(ctx, fn) { return function() { return fn.apply(ctx, arguments); } }
  200. 200. 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)); }; }
  201. 201. 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!
  202. 202. Intermedio: herencia funcional function PseudoConstructor() { var self = {}; self.propiedad = "valor"; return self; } var pseudoInstancia = PseudoConstructor(); pseudoInstancia.propiedad; // "valor"
  203. 203. 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");
  204. 204. Intermedio: herencia funcional function PseudoConstructor() { var self = {}; self.propiedad = "valor"; self.metodo = function(nombre) { return "No te duermas, " + nombre + "!"; }; return self; } var pseudoInstancia = PseudoConstructor(); pseudoInstancia.metodo("Abraham");
  205. 205. 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");
  206. 206. 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
  207. 207. Intermedio: herencia funcional ¿Y para heredar?
  208. 208. 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
  209. 209. Intermedio: herencia funcional ¿Cómo llamar al supermétodo?
  210. 210. 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; }
  211. 211. 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
  212. 212. Intermedio: herencia funcional “Herencia funcional”: ✓ Explotar clausuras y objetos en linea ✓ Extremadamente simple e intuitivo ✓ Mejor encapsulado público/privado ✓ Poco ruido sintáctico ✓ No hacen falta helpers ni librerías ๏ Un poco... ¿cutre? ๏ No es la manera más popular ๏ ¡Peor uso de la memoria!
  213. 213. Programación Funcional made with love by Redradix (www.redradix.com)
  214. 214. ¿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
  215. 215. Funciones de orden superior Funciones que devuelven funciones • curry • bind • ¡Muchas otras!
  216. 216. Funciones de orden superior Algunas de las más útiles: • throttle • debounce • once • after • compose • memoize
  217. 217. throttle Controlar la frecuencia de invocación • La función se invocará como máximo una vez • Durante el periodo de tiempo especificado
  218. 218. throttle var counter = 0, inc = function() { counter++; }; inc = throttle(inc, 10); for (var i=100000; i--;) { inc(); } alert(counter); // ~6
  219. 219. 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); } } }
  220. 220. debounce Ejecutar la función cuando se deje de llamar • La llamada se pospone hasta que pasen x ms • Desde la última invocación
  221. 221. debounce var counter = 0, inc = function() { counter++; alert(counter); }; inc = debounce(inc, 1000); for (var i=100000; i--;) { inc(); }
  222. 222. 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); } }
  223. 223. 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);
  224. 224. once function once(fn) { var executed = false; return function() { if (!executed) { executed = true; return fn.apply(this, arguments); } } }
  225. 225. 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);
  226. 226. after function after(fn, n) { var times = 0; return function() { times++; if (times % n == 0) { return fn.apply(this, arguments); } } }
  227. 227. 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());
  228. 228. 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; } }
  229. 229. 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
  230. 230. 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);
  231. 231. 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]; } }
  232. 232. Asincronía JS es, por naturaleza, asíncrono • Eventos • AJAX • Carga de recursos
  233. 233. Asincronía ¿Qué significa asíncrono? function asincrona() { var random = Math.floor(Math.random() * 100); setTimeout(function() { return random; }, random); }
  234. 234. Asincronía ¿Cómo devuelvo el valor random desde dentro? function asincrona() { var random = Math.floor(Math.random() * 100); setTimeout(function() { return random; }, random); }
  235. 235. Asincronía function asincrona(callback) { var random = Math.floor(Math.random() * 1000); setTimeout(function() { callback(random); }, random); } asincrona(function(valor) { alert(valor); });
  236. 236. Asincronía function asincrona(callback) { var random = Math.floor(Math.random() * 1000); setTimeout(function() { callback(random); }, random); } asincrona(function(valor) { alert(valor); });
  237. 237. Asincronía function asincrona(callback) { var random = Math.floor(Math.random() * 1000); setTimeout(function() { callback(random); }, random); } asincrona(function(valor) { alert(valor); });
  238. 238. Asincronía Promesas • Otra forma de escribir código asíncrono • Más fácil de manipular • Más fácil de combinar
  239. 239. 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
  240. 240. Promesas function onSuccess(data) { /* ... */ } function onFailure(e) { /* ... */ } var promesa = $.get('/mydata'); promesa.then(onSuccess, onFailure);
  241. 241. 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
  242. 242. 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
  243. 243. 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. } });
  244. 244. 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. });
  245. 245. 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. });
  246. 246. 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); });
  247. 247. Promesas Casos: cuando onSuccess devuelve un valor /* siendo promise una promesa... */ promise.then(function() { return 42; }).then(function(valor) { return "La respuesta es " + valor; }).then(function(mensaje) { console.log(mensaje); });
  248. 248. 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!"); });
  249. 249. 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..."); });
  250. 250. 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); });
  251. 251. Promesas Casos: capturar errores /* siendo promise una promesa... */ promise.then(function() { throw new Error("Oops!"); }).then(function() { console.log("Nunca llegamos aquí..."); }, function(e) { console.log("Vaya por Dios!"); console.log(e); });
  252. 252. 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); });
  253. 253. Promesas Casos: cascada de errores /* siendo promise una promesa... */ promise.then(function() { throw new Error("Oh no!"); }).then(function() { console.log("Nunca se ejecuta."); }).then(function() { console.log("Esto tampoco."); }, function(e) { console.log("Vaya por Dios!"); console.log(e); });
  254. 254. 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! */ });
  255. 255. Promesas Casos: errores localizados /* siendo promise una promesa... */ promise.then(function() { throw new Error("Oh no!"); }).then(function() { console.log("Nunca se ejecuta."); }, function(e) { console.log("Manejador del error"); }).then(function() { /* ... */ }, function(e) { /* este manejador no se ejecuta! */ });
  256. 256. Promesas ¿Cómo creo una promesa?
  257. 257. 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
  258. 258. Promesas Promesa Diferido Representa un valor futuro Controla la generación del valor onSuccess resolve(valor) onFailure reject(error)
  259. 259. 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()); });
  260. 260. 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
  261. 261. 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
  262. 262. Promesas Deferred#promise() • Devuelve la promesa asociada al diferido
  263. 263. Promesas Deferred#then(onSuccess, onFailure) • Exactamente igual que hacer: deferred.promise().then(...);
  264. 264. 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
  265. 265. 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 ...])
  266. 266. 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
  267. 267. 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
  268. 268. Promesas R.Deferred.when(1, 2, 3).then(function(a, b, c) { console.log(a, b, c); // 1 2 3 });
  269. 269. 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);
  270. 270. Promesas /* Homogeneizar */ var promesaOValor = noSeQueDevuelve(); R.Deferred.when(promesaOValor).then(function(valor) { console.log(valor); });
  271. 271. 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);
  272. 272. 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");
  273. 273. Promesas Implementa R.Deferred.when() • tema2/when/index.html
  274. 274. Patrones y principios de diseño made with love by Redradix (www.redradix.com)
  275. 275. 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.)
  276. 276. SRP Es común ver cosas como esta: $.ajax({ ... }) .success(function() { cambioEnInterfaz(); mostrarModal(); if ($("#elemento").value() == "Ok") { /* ... */ } globalSeHaGuardado = true; }) .error(function() { // ... });
  277. 277. SRP O como esta: var Widget = Class.extend({ onClick: function() { ... }, guardar: function() { ... }, render: function() { ... }, mostrarError: function() { ... } });
  278. 278. 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() { ... } });
  279. 279. SRP Caso práctico: masonry.js • https://github.com/desandro/masonry/blob/master/ jquery.masonry.js • en el método _create (línea 102)...
  280. 280. 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(); !!!!!!!!}); !!!!!!}
  281. 281. 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(); !!!!!!!!}); !!!!!!}
  282. 282. 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
  283. 283. SRP Caso práctico: BrowserQuest • https://github.com/mozilla/BrowserQuest/blob/ master/client/js/chest.js
  284. 284. 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; !!!!!!!!} !!!!});
  285. 285. 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
  286. 286. SRP Caso práctico: Backbone.js • https://github.com/documentcloud/backbone/blob/ master/backbone.js • en Backbone.Model, línea 179...
  287. 287. 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”)
  288. 288. 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
  289. 289. Principios de diseño • Tell, Don’t Ask - “Dime lo que necesitas” - Claridad y expresividad - Encapsular las comprobaciones
  290. 290. 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)]; !!!!!!!!!!!!!}
  291. 291. 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)]; !!!!!!!!!!!!!}
  292. 292. Tell, Don’t Ask Programación: • Estructurada: adquiere info y toma decisiones • OO: manda a los objetos hacer cosas
  293. 293. 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
  294. 294. Tell, Don’t Ask El error: 1. Preguntar a un objeto sobre su estado 2. Tomar una decisión 3. Decirle lo que tiene que hacer ¡Probablemente ese código pertenece al objeto!
  295. 295. Tell, Don’t Ask if (usuario.primerLogin) { usuario.mostrarMensajeBienvenida(); } else { usuario.mostrarSaludo(); }
  296. 296. Tell, Don’t Ask var Usuario = Class.extend({ saludar: function() { if (this.primerLogin) { this.mostrarMensajeBienvenida(); } else { this.mostrarSaludo(); } } }); // y después... usuario.saludar();
  297. 297. Tell, Don’t Ask function comprobarTimeout(respuesta) { if ((Date.now() - respuesta.start) > 10000) { respuesta.notificarTimeout(); } }
  298. 298. Tell, Don’t Ask var Respuesta = Class.extend({ comprobarTimeout: function() { if ((Date.now() - this.start) > 10000) { this.notificarTimeout(); } } }); // y después... respuesta.comprobarTimeout();
  299. 299. Tell, Don’t Ask var elementos = miColeccion.getItems(); for (var i=0; i<elementos.length; i++) { var elemento = elementos[i]; console.log(elemento.nombre); }
  300. 300. Tell, Don’t Ask miColeccion.forEach(function(e) { console.log(e.nombre); });
  301. 301. Tell, Don’t Ask var elemento = new Elemento("hola", 12); var lista = miColeccion.getItems(); lista.addElementAt(elemento.getOrder(), elemento);
  302. 302. Tell, Don’t Ask var elemento = new Elemento("hola", 12); miColeccion.add(elemento);
  303. 303. 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!
  304. 304. 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
  305. 305. Principios de diseño • S.O.L.I.D. - Single Responsibility - Open-Closed - Liskov Substitution - Interface Segregation - Dependency Inversion
  306. 306. 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
  307. 307. 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();
  308. 308. 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();
  309. 309. Open-Closed Pretende: • Promover el uso de abstracciones • Código modular y flexible ante el cambio • Evitar un torrente de cambios en cascada!
  310. 310. 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
  311. 311. Open-Closed var Canvas = Class.extend({ render: function(figura) { if (figura instanceof Triangulo) { // ... } else if (figura instanceof Cuadrado) { // ... } } });
  312. 312. 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) { ...} });
  313. 313. 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
  314. 314. 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!)!{ //!... }
  315. 315. Open-Closed Caso práctico: jasmine.js •https://github.com/pivotal/jasmine/blob/master/src/ core/Reporter.js
  316. 316. Open-Closed /**!No3op!base!class!for!Jasmine!reporters. !* !*!@constructor !*/ jasmine.Reporter!=!function()!{ }; //noinspection!JSUnusedLocalSymbols jasmine.Reporter.prototype.reportRunnerStarting!=!function(runner)!{ }; //noinspection!JSUnusedLocalSymbols jasmine.Reporter.prototype.reportRunnerResults!=!function(runner)!{ }; //...
  317. 317. 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
  318. 318. Sustitución de Liskov var Animal = Class.extend({ caminar: function() { /*...*/ }, comer: function() { /* .. */ } });
  319. 319. Sustitución de Liskov var Animal = Class.extend({ caminar: function() { /*...*/ }, comer: function() { /* ... */ } }); var Serpiente = Animal.extend({ /* ... */ }); var s = new Serpiente(); s.caminar();
  320. 320. 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”
  321. 321. 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
  322. 322. Segregación de la intefaz var Modal = Class.extend({ show: function() { ... }, hide: function() { ... } });
  323. 323. Segregación de la intefaz var Modal = Class.extend({ show: function() { ... }, hide: function() { ... } }); var Timer = Class.extend({ setTimer: function(time) { ... }, startTimer: function() { ... }, onTimeout: function() { ... } });
  324. 324. Segregación de la intefaz var Modal = Class.extend({ show: function() { ... }, hide: function() { ... } }); var Timer = Class.extend({ setTimer: function(time) { ... }, startTimer: function() { ... }, onTimeout: function() { ... } }); var TimedModal = Modal.extend({ init: function() { this.setTimer(100); this.onTimeout = this.hide; this.show(); this.startTimer(); } });
  325. 325. 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(); } });
  326. 326. Segregación de la intefaz var WidgetView = Class.extend({ init: function(cont) { var cbind = curry(bind, cont); $("#E1").click(cbind(cont.onE1Click)); } });
  327. 327. 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)); } });
  328. 328. Segregación de la intefaz var WidgetView = Class.extend({ init: function(cont) { var cbind = curry(bind, cont); $("#E1").click(cbind(cont.onE1Click)); $("#E2").click(cbind(cont.onE2Click)); $("#E3").click(cbind(cont.onE3Click)); $("#E4").click(cbind(cont.onE4Click)); $("#save").click(cbind(cont.save)); $("#reset").click(cbind(cont.reset)); $("#validate").click(cbind(cont.validate)); $("#next-page").click(cbind(cont.getNextPage)); // ... } });
  329. 329. 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)); // ... } });
  330. 330. Segregación de la intefaz var WidgetView = Class.extend({ init: function(viewCont, cont, pagCont) { var vbind = curry(bind, cont), cbind = curry(bind, cont), pbind = curry(bind, pagCont); $("#E1").click(vbind(viewCont.onE1Click)); $("#E2").click(vbind(viewCont.onE2Click)); $("#E3").click(vbind(viewCont.onE3Click)); $("#E4").click(vbind(viewCont.onE4Click)); $("#save").click(cbind(cont.save)); $("#reset").click(cbind(cont.reset)); $("#validate").click(cbind(cont.validate)); $("#next-page").click(pbind(pagCont.getNextPage)); // ... } });
  331. 331. 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.
  332. 332. 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); } });
  333. 333. Dependency Inversion var Model = Class.extend({ save: function() { var tbind = curry(bind, this), stop = bind(this.icon, this.icon.stop); $.post(this.url, this.getData()) .success(tbind(this.saved)) .error(tbind(this.saveFailed)) .complete(stop); } });
  334. 334. 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) { }); }
  335. 335. 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(); } });
  336. 336. Dependency Inversion Caso práctico: backbone.js • https://github.com/documentcloud/backbone/blob/ master/backbone.js • Backbone.Model#fetch, línea 335
  337. 337. 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); !!!!},
  338. 338. Patrones de organización
  339. 339. Patrones de organización • Parámetros con nombre/por defecto • Módulos y namespaces • Control de acceso • Mixins
  340. 340. Parámetros con nombre function ajax(url, data, method, success, error, complete) { url || url = "/"; data || data = {}; method || method = "POST"; // ... } ajax("/", {}, "GET", function(){ ... }, function() { ... }, function() { ... });
  341. 341. Parámetros con nombre function ajax(options) { var url = options.url || "/", data = options.data || {}, method = options.method || "POST"; //... } ajax({data: [1, 2], complete: function() { ... }});
  342. 342. 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() { ... }});
  343. 343. 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; !!!!},
  344. 344. Intermedio: merge ¿Cómo sería esa función merge?
  345. 345. 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; }
  346. 346. 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
  347. 347. Módulos y namespaces • JavaScript no tiene concepto de namespace • Todo tirado en objeto global - Mucha polución - Colisión de nombres - Difícil de navegar •¡Pero tenemos funciones!
  348. 348. Módulos y namespaces function miHelper() { // ... } var miVariableTemporal = 0; var estadoLocal = {};
  349. 349. Módulos y namespaces function sandbox() { function miHelper() { // ... } var miVariableTemporal = 0; var estadoLocal = {}; }
  350. 350. Módulos y namespaces (function sandbox() { function miHelper() { // ... } var miVariableTemporal = 0; var estadoLocal = {}; }())
  351. 351. Módulos y namespaces (function sandbox() { function miHelper() { // ... } var miVariableTemporal = 0; var estadoLocal = {}; }())
  352. 352. Módulos y namespaces function miFuncionUtil() { // ... } function miGranMetodo() { // ... } function miEstupendoHelper() { // ... }
  353. 353. Módulos y namespaces function aux() { } var state = "off"; function miFuncionUtil() { // ... } function miGranMetodo() { // ... } function miEstupendoHelper() { // ... }
  354. 354. Módulos y namespaces (function() { function aux() { } var state = "off"; function miFuncionUtil() { } function miGranMetodo() { } function miEstupendoHelper() { } }())
  355. 355. Módulos y namespaces var Modulo = (function() { function aux() { } var state = "off"; function miFuncionUtil() { } function miGranMetodo() { } return { miFuncionUtil: miFuncionUtil, miGranMetodo: miGranMetodo }; }());
  356. 356. Módulos y namespaces var Modulo = (function() { function aux() { } var state = "off"; function miFuncionUtil() { } function miGranMetodo() { } return { miFuncionUtil: miFuncionUtil, miGranMetodo: miGranMetodo }; }());
  357. 357. Módulos y namespaces Modulo.miFuncionUtil();
  358. 358. Módulos y namespaces var Modulo = {}; (function(Modulo) { function aux() { } var state = "off"; Modulo.miFuncionUtil = function() { } Modulo.miGranMetodo = function() { } }(Modulo));
  359. 359. Módulos y namespaces var Modulo = {}; (function(Modulo) { function aux() { } var state = "off"; Modulo.miFuncionUtil = function() { } }(Modulo)); (function(Modulo) { Modulo.miGranMetodo = function() { } }(Modulo));
  360. 360. 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 || {}));
  361. 361. Módulos y namespaces var Modulo = (function(Modulo) { function aux() { } var state = "off"; Modulo.miFuncionUtil = function() { } return Modulo; }(Modulo || {})); var Modulo = (function(Modulo) { Modulo.miGranMetodo = function() { } return Modulo; }(Modulo || {}));
  362. 362. 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
  363. 363. 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
  364. 364. 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 || {}));
  365. 365. Módulos y namespaces var Modulo = (function (Modulo) { var _private = Modulo._private; // acceso a _private }(Modulo || {}));
  366. 366. Módulos y namespaces Modulo._seal();
  367. 367. Intermedio: mejorar klass.js Convierte klass.js en un módulo Class
  368. 368. 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));
  369. 369. Módulos y namespaces Según crece la aplicación...
  370. 370. Módulos y namespaces Según crece la aplicación... var MiLibreria = MiLibreria || {};
  371. 371. Módulos y namespaces Según crece la aplicación... var MiLibreria = MiLibreria || {}; MiLibreria.widgets = MiLibreria.widgets || {};
  372. 372. 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 || {}));
  373. 373. 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({ ... }); });
  374. 374. Intermedio: namespace ¿Como sería la función MiLib.namespace?
  375. 375. Intermedio: namespace ¿Como sería la función MiLib.namespace? var MiLib = (function(my) { my.namespace = function(string, sandbox) { // ??? }; return my; }(MiLib || {}));
  376. 376. 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 || {}));
  377. 377. 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
  378. 378. Mixins var Mixin = function() {}; Mixin.prototype = { inspect: function() { var output = []; for(key in this) { output.push(key + ': ' + this[key]); } return output.join(', '); } };
  379. 379. Mixins var Persona = Class.extend({ init: function(nombre) { this.nombre = nombre; } }); augment(Persona, Mixin); var pepito = new Persona("Pepito"); pepito.inspect();
  380. 380. Mixins function augment(target, source) { for (var prop in source.prototype) { if(!target.prototype[prop]) { target.prototype[prop] = source.prototype[prop]; } } }
  381. 381. Intermedio: Mejores Mixins Lo podemos hacer mejor! • Mejor integración con klass.js • Callbacks de inclusión (estilo ruby)
  382. 382. 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);
  383. 383. Patrones de creación de objetos
  384. 384. Factoría Delegar la creación de un objeto • Elegir el constructor dinámicamente • Procesos de construcción complejos • Desacoplar detalles de implementación
  385. 385. 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
  386. 386. Factoría var GlobalConfig = {locale: "es"};
  387. 387. Factoría var GlobalConfig = {locale: "es"}; I18n.withCurrentLocale = function() { var instance = new this; instance.locale = GlobalConfig.locale; return instance; };
  388. 388. Factoría var i18n = I18n.withCurrentLocale(); alert(i18n.translate('header.title'));
  389. 389. 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(); } };
  390. 390. Factoría Controlar las instancias var Enemigo = (function() { var enemigos = []; var Enemigo = Class.extend({ /*...*/ }); Enemigo.crear = function() { var instance; if (enemigos.length < 5) { instance = new Enemigo(); enemigos.push(instance); return instance; } else { throw new Error("No puede haber más!"); } } return Enemigo; }());
  391. 391. 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; }());
  392. 392. 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
  393. 393. Singleton Clase con una única instancia • Un tipo peculiar de Factoría • Cuando no tiene sentido más de una instancia
  394. 394. 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())
  395. 395. Patrones de abstracción
  396. 396. Patrones de abstracción • Iteradores • Decorador • Fachada • Estrategia • Inyección de dependencias • Proxy
  397. 397. Iteradores Recorrer una colección • Sin revelar detalles de implementación • Mayor control sobre la iteración
  398. 398. Iteradores var ListadoAlumnos = Class.extend({ init: function() { this.alumnos = []; }, add: function(nombre, ciudad) { this.alumnos.push({ nombre: nombre, ciudad: ciudad }); } });
  399. 399. 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");
  400. 400. 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); } }
  401. 401. 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); } }
  402. 402. 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); } } });
  403. 403. 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); });
  404. 404. 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++]; } });
  405. 405. 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); }
  406. 406. 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); } });

×