Saltar al contenido

Usando el patrón del prototipo de JavaScript

Este es el segundo post de una serie que cubre cómo los patrones pueden ser usados para estructurar el código JavaScript. En mi anterior post introduje lo que llamo «código de función de espaguetis» y expliqué algunos de los problemas que introduce. También hablé del impacto de las variables globales y cómo los cierres añaden una solución muy necesaria. En este post presentaré el Patrón Prototipo y mostraré cómo se basa en la funcionalidad incorporada en el lenguaje JavaScript.

Usando el patrón del prototipo de JavaScript
Usando el patrón del prototipo de JavaScript

El Patrón de Prototipos puede dividirse en dos secciones principales, incluyendo una sección de construcción y una sección de prototipos. El prototipo permite asociar funciones y propiedades a los objetos. Sin embargo, en lugar de que cada instancia de objeto obtenga una copia de todas las funciones/propiedades cada vez que se crea un objeto, sólo existe un conjunto de funciones/propiedades en todos los objetos, lo que resulta en un menor consumo de memoria. En otras palabras, las funciones y propiedades se definen una vez por prototipo en lugar de una vez por objeto.

Como una revisión rápida, mostré el siguiente código en un post anterior. El código simplemente enumera todas las funciones directamente sin encapsular y define varias variables globales. Mientras que el código funciona bien de esta manera, examinaré cómo podemos reestructurarlo para seguir el patrón del prototipo.

window.onload = function () {eqCtl = document.getElementById($0027eq$0027);currNumberCtl = document. getElementById($0027currNumber$0027);};var eqCtl,currNumberCtl,operator,operatorSet = false,equalsPressed = false,lastNumber = null;function add(x,y) {return x + y;}function rest(x, y) {return x - y; }función multiplicar(x, y) {retorno x * y;}función dividir(x, y) {si (y == 0) {alerta("No se puede dividir por 0");retorno 0;}retorno x / y;}función fijarVal(val) {currNumberCtl. innerHTML = val;}function setEquation(val) {eqCtl. innerHTML = val;}función clearNumbers() {lastNumber = null;equalsPressed = operatorSet = false;setVal($00270$0027);setEquation($0027$0027); }función setOperador(nuevoOperador) {if (nuevoOperador == $0027=$0027) {igualPresionado = true;calculate();setEquation($0027$0027);return;}//Manejo caso donde = fue presionado//seguido por un operador (+, -, *, /)if (! igual aPresionado) calculate();igualPresionado = falso;operador = nuevoOperador;operadorSet = true;lastNumber = parseFloat(currNumberCtl. innerHTML);var eqText = (eqCtl.innerHTML == $0027$0027) ? lastNumber + $0027 $0027 + operador + $0027 $0027 : eqCtl.innerHTML + $0027 $0027 + operador + $0027 $0027;setEquation(eqText);} function numberClick(e) {var button = (e.target) ? e.target : e.srcElement;if (operatorSet == true || currNumberCtl. innerHTML == $00270$0027) {setVal($0027$0027);}función calculate() {if (!operator || lastNumber == null) return;var currNumber = parseFloat(currNumberCtl. innerHTML),newVal = 0;//eval() habría hecho esto mucho más simple// pero no quería usarlo en favor de un conjunto de métodos más//"robustos" para demostrar el patrónsswitch (operador) {case $0027+$0027:newVal = add(lastNumber, currNumber);break;case $0027-$0027: newVal = restar(lastNumber, currNumber);break;case $0027*$0027:newVal = multiplicar(lastNumber, currNumber);break;case $0027/$0027:newVal = dividir(lastNumber, currNumber);break;}setVal(newVal);lastNumber = newVal;}

Para empezar a usar el patrón del prototipo, primero hay que crear un constructor como se muestra a continuación. El constructor puede aceptar uno o más parámetros y definir cualquier variable que el objeto necesite. Observe que las variables se ajustan al objeto en lugar de al ámbito global.

var Calculadora = function (tb, eq) {this.eqCtl = document.getElementById(eq);this.currNumberCtl = document.getElementById(tb);this.operator = null;this.operatorSet = false;this.equalsPressed = false;this.lastNumber = null;};

Una vez definido el constructor, se puede crear un prototipo usando la palabra clave prototipo. Aunque se puede crear un prototipo para cada función, es más conveniente (y menos teclear) aprovechar la sintaxis de estilo JSON donde el nombre de la función representa el nombre de la propiedad JSON y el valor representa la función. A continuación se muestra un ejemplo de definición de dos funciones en un prototipo. Observe que cada función se separa con una coma, tal como se separarían las propiedades definidas en un objeto JSON normal. Oficialmente se llama un objeto de JavaScript literal, pero si estás familiarizado con JSON utiliza la misma sintaxis estándar:

Calculador.prototipo = {añadir: función (x, y) {retorno x + y;}, restar: función (x, y) {retorno x - y;}}

El código original de la calculadora basada en funciones mostrado anteriormente puede ser refactorizado para seguir el patrón del prototipo como se muestra a continuación. Se crea un prototipo para el objeto Calculadora y se definen las funciones/propiedades dentro del prototipo usando la sintaxis literal del objeto JavaScript.

Calculadora. prototipo = {suma: función (x, y) {retorno x + y;},resta: función (x, y) {retorno x - y;},multiplica: función (x, y) {retorno x * y;},divide: función (x, y) {si (y == 0) {alerta("No se puede dividir por 0");}retorno x / y;},setVal: función(val) {esto. currNumberCtl.innerHTML = val;},setEquation: function(val) {this.eqCtl.innerHTML = val;},clearNumbers: function () {this. lastNumber = null;this.equalsPressed = this.operatorSet = false;this.setVal($00270$0027);this.setEquation($0027$0027);},setOperator: function (newOperator) {if (newOperator == $0027=$0027) {this. igualPresionado = true;this.calculate();this.setEquation($0027$0027);return;}//Handle case where = was pressed//followed by an operator (+, -, *, /)if (! this.equalsPressed) this.calculate();this.equalsPressed = false;this.operator = newOperator;this.operatorSet = true;this.lastNumber = parseFloat(this.currNumberCtl.innerHTML);var eqText = (this.eqCtl.innerHTML == $0027$0027) ?this. lastNumber + $0027 $0027 + this.operator + $0027 $0027 :this.eqCtl.innerHTML + $0027 $0027 + this.operator + $0027 $0027;this.setEquation(eqText);},numberClick: function () {var button = (event.target) ? event.target : event.srcElement;if (this.operatorSet == true || this.currNumberCtl.innerHTML == $00270$0027) {this. setVal($0027$0027);this.operatorSet = false;}this.setVal(this.currNumberCtl.innerHTML + button.innerHTML);this.setEquation(this.eqCtl.innerHTML + button.innerHTML);},calculate: function () {if (!this.operator ||| this.lastNumber == null) return;var displayedNumber = parseFloat(this. currNumberCtl.innerHTML)newVal = 0;//eval() habría hecho esto mucho más simple// pero no quería usarlo en favor de un conjunto de métodos más//"robustos" para demostrar el cambio de patrones (this.operator) {case $0027+$0027:newVal = this.add(this.lastNumber, displayedNumber);break;case $0027-$0027:newVal = this. rest(this.lastNumber, displayedNumber);break;case $0027*$0027:newVal = this.multiplicate(this.lastNumber, displayedNumber);break;case $0027/$0027:newVal = this.divide(this.lastNumber, displayedNumber);break;}this.setVal(newVal);this.lastNumber = newVal;}};

Para usar el objeto Calculadora, cree una nueva instancia y pase los nombres de los objetos contenedores HTML al constructor (los objetos contenedores identifican los ID de los controles usados para mostrar los cálculos – vea el código HTML más abajo):

var calc = null;window.onload = function () {calc = new Calculator($0027currNumber$0027, $0027eq$0027);};

El HTML utilizado para renderizar la calculadora puede hacer referencia al objeto calc creado cuando se carga la página. El siguiente código demuestra cómo se puede hacer esto para manejar los eventos a medida que se hace clic en los diferentes elementos div. Normalmente prefiero cablear los eventos a los manejadores de eventos usando jQuery en aplicaciones del «mundo real» para mantener el HTML limpio, pero quise centrarme en los patrones de JavaScript y evitar introducir librerías adicionales para este post. No soy un gran fan de definir onclick y otros manejadores de eventos directamente en elementos HTML pero lo dejo pasar para este post.

<div onclick="calc. numberClick(event);"³;7</div><div onclick="calc.numberClick(event);"³;8</div><div onclick="calc.numberClick(event);"³;9</div> <div onclick="calc. setOperador($0027/$0027);"/</div;;div rowspan2" onclick="calc.clearNumbers(); "C</div;;/div;;div onclick="calc. numberClick(event); "4</div><div onclick="calc.numberClick(event); "5</div><div onclick="calc.numberClick(event); "6</div> <div onclick="calc. setOperador($0027*$0027); "*</div; </div;;div onclick="calc.numberClick(event); "1</div;;div onclick="calc. numberClick(event);";2</div><div onclick="calc.numberClick(event);">3</div><div onclick="calc.setOperator($0027-$0027)"-</div;;div rowspan2" onclick="calc. setOperador($0027=$0027);"=</div;;------;div colspan2" onclick="calc.numberClick(event);"-0</div;²;div onclick="calc. numberClick();">.</div><div onclick="calc.setOperator($0027+$0027);">+</div;;

El patrón del prototipo proporciona una buena manera de estructurar el código JavaScript, pero hay varios otros caminos que puede recorrer si lo desea. En este próximo post hablaré del Patrón del Módulo Revelador y explicaré cómo puede ser usado.

Demostraciones de todos los patrones cubiertos en esta serie pueden ser descargados abajo.

Este post cubre los patrones y técnicas encontradas en el curso de Estructuración de Código JavaScript de Dan Wahlin en Pluralsight.com. Si estás en Twitter sigue a Dan en @DanWahlin.