Estos patrones ayudan a organizar el código, a mejorar su performance, a la creación de objetos y a aplicar características perdidas en JS como “propiedades privadas”
-
Las 3 capas de una página web son
- Content (HTML)
- Presentation (CSS)
- Behavior (JavaScript)
-
Estas capas deben estar lo más separadas posibles, es decir no debemos tocar directamente desde una capa elementos pertenecientes a otra capa.
Esto, en la capa de comportamiento (Javascript) significa:
- Evitar los tags
<script>
con código javascript dentro del marcado (intentar utilizar sólo los que carguen otros archivos) - Evitar attributos inline en las etiquetas como
onclick
,onmouseover
etc.. (mejor capturar el evento desde un archivo externo utilizando los métodosaddEventListener
o `attachEvent) - No alterar directamente propiedades del CSS (en todo caso, añadir o quitar clases)
- Añadir dinámicamente el marcado que no tenga sentido con el Javascript desactivado
- Que el código JS quede en archivos *.js separados que incluyamos en la página
- Evitar los tags
[creación de objetos]
-
Debemos evitar el uso de variables globales para minimizar las posibles colisiones de nombres de variable.
-
Una manera de hacer esto es utilizando un Namespace, es decir, creando un único objeto global y haciendo que todas las variables y funciones que necesitemos sean propiedades de este objeto
// global namespace
var MYAPP = MYAPP || {};
// sub-object
MYAPP.event = {};
// object together with the method declarations
MYAPP.event = {
addListener: function(el, type, fn) {
// .. do the thing
},
removeListener: function(el, type, fn) {
// ...
},
getEvent: function(e) {
// ...
}
// ... other methods or properties
};
MYAPP.dom = {};
MYAPP.dom.style = {};
- Podemos crear un método que nos ayude con la creación de elementos en el Namespace
var MYAPP = MYAPP || {};
MYAPP.namespace = function(name){
var parts = name.split('.');
var current = MYAPP;
for (var i in parts) {
if (!current[parts[i]]) {
current[parts[i]] = {};
}
current = current[parts[i]];
}
}
>>> typeof (MYAPP.dom.style) === "object"
TypeError: MYAPP.dom is undefined
>>> MYAPP.namespace('dom.style')
>>> typeof (MYAPP.dom.style) === "object"
true
[performance]
- Cuando sabemos que cierta condición no va a cambiar durante la ejecución del programa, podemos hacer que nuestro código evalué esa condición una sola vez (durante la carga del programa) y que devuelva funciones “customizadas” según estas condiciones (ej.- Feature Detection)
var MYAPP = {};
MYAPP.event = {
addListener: null,
removeListener: null
};
if (typeof window.addEventListener === 'function') {
MYAPP.event.addListener = function(el, type, fn) {
el.addEventListener(type, fn, false);
};
MYAPP.event.removeListener = function(el, type, fn) {
el.removeEventListener(type, fn, false);
};
} else if (typeof document.attachEvent === 'function'){ // IE
MYAPP.event.addListener = function(el, type, fn) {
el.attachEvent('on' + type, fn);
};
MYAPP.event.removeListener = function(el, type, fn) {
el.detachEvent('on' + type, fn);
};
} else { // older browsers
MYAPP.event.addListener = function(el, type, fn) {
el['on' + type] = fn;
};
MYAPP.event.removeListener = function(el, type, fn) {
el['on' + type] = null;
};
};
[performance]
- La Lazy Definition es muy parecida al Init-time branching, con la diferencia es que aquí la “customización” se realiza solo cuando se llama a la función por primera vez
var MYAPP = {};
MYAPP.myevent = {
addListener: function(el, type, fn){
if (typeof el.addEventListener === 'function') {
MYAPP.myevent.addListener = function(el, type, fn) {
el.addEventListener(type, fn, false);
};
} else if (typeof el.attachEvent === 'function'){
MYAPP.myevent.addListener = function(el, type, fn) {
el.attachEvent('on' + type, fn);
};
} else {
MYAPP.myevent.addListener = function(el, type, fn) {
el['on' + type] = fn;
};
}
MYAPP.myevent.addListener(el, type, fn);
}
};
[creación de objetos]
-
Cuando una función necesita muchos parámetros (más de 3 por ejemplo) es una buena idea agruparlos en un objeto de configuración y utilizar éste como único parámetro (las propiedades del objeto serán los parámetros).
-
Algunas ventajas de utlizar un objeto de configuracion son:
- El orden no importa
- Puedes setear sólo los parámetros que necesites
- La función queda más escalable
- El código queda más legible (los nombres de las propiedades están presentes en la llamada)
// a constructor that creates buttons
var MYAPP = {};
MYAPP.dom = {};
MYAPP.dom.Button = function(text, conf) {
var type = conf.type || 'submit';
var font = conf.font || 'Courier';
var color = conf.color || 'white';
var b = document.createElement('input');
b.type = type;
b.font = font;
b.color = color;
b.value = text;
return b;
}
document.body.appendChild(new MYAPP.dom.Button('puuush'));
var config = {
font: 'Arial, Verdana, sans-serif',
color: 'white'
};
var oMyButton = new MYAPP.dom.Button('puuush', config);
document.body.appendChild(oMyButton);
var oMyButton2 = new MYAPP.dom.Button('puuush', {color: 'red'});
document.body.appendChild(oMyButton2);
[creación de objetos]
- Podemos definir propiedades/metodos privados en un objeto (aunque Javascript no tenga una sintaxis especial para ello) usando variables locales en la función constructora
var MYAPP = {};
MYAPP.dom = {};
MYAPP.dom.Button = function(text, conf) {
// private property
var _styles = {
font: 'Verdana',
border: '1px solid black',
color: 'black',
};
// private method
function _setStyles() {
for (var i in _styles) {
b.style[i] = conf[i] || _styles[i];
}
}
conf = conf || {};
var b = document.createElement('input');
b.type = conf['type'] || 'submit';
b.value = text;
_setStyles();
return b;
};
var oMyButton = new MYAPP.dom.Button('puuush', {color: 'blue'});
document.body.appendChild(oMyButton);
Para mejorar la legibilidad las variables/métodos privados se marcan con “_” delante del nombre
[creación de objetos]
[http://www.crockford.com/javascript/private.html](http://www.crockford.com/javascript/private.html)
- Metodos Privilegiados (según Douglas Crockford) son métodos públicos que pueden acceder a métodos o propiedades privadas Actuan de filtro para hacer accesible alguna funcionalidad privada pero de una manera controlada Son los metodos que definimos en la función constructora como parte del this, a diferencia de los metodos definidos en el prototipo.
var Person = function(options){
//private properties
var _name = options.name
var _birthYear = options.birthYear;
//private method
var _calculateAge = function(){
var today = new Date();
return today.getFullYear() - _birthYear;
}
//Privileged methods
this.getAge = function(){
return _calculateAge(); //calling private method
}
this.getName = function(){
return _name.toUpperCase(); //return private variable filtered
}
}
//new Person instance
var myPerson = new Person( {name:'Peter', birthYear:1983} );
>>> myPerson.getAge() === 29
true
>>> myPerson.name === 'Peter'
false
>>> myPerson.name === undefined
true
>>> myPerson.getName() === 'PETER'
true
[creación de objetos]
http://www.crockford.com/javascript/private.html
http://blog.alexanderdickson.com/javascript-revealing-module-pattern
http://blog.davidlitmark.com/post/6009004931/an-introduction-to-the-revealing-module-pattern
- Si queremos mantener intacta una función para nuestro “código interno”, pero también queremos dar visibilidad externa a esa función, podemos asignar esa función a una propiedad pública. A este patrón tambien se le conoce como Revealing Module Pattern
var MYAPP = {};
MYAPP.dom = (function(){
var _setStyle = function(el, prop, value) {
return 'setStyle';
};
var _getStyle = function(el, prop) {
return 'getStyle';
};
return {
setStyle: _setStyle,
getStyle: _getStyle,
yetAnother: _setStyle
}; })()
MYAPP.dom.setStyle = function(){ return ‘Hey, Ho, Let’s Go!!’ };
>>> MYAPP.dom.setStyle() === 'Hey, Ho, Let’s Go!!'
true
>>> MYAPP.dom.yetAnother() === 'setStyle'
true
[creación de objetos]
- Otro patrón útil para crear objetos es utilizar una función anónima que devuelva un objeto y ejecutarla inmediatamente De esta manera las variables dentro de la función son locales (declaradas con var) y son destruidas cuando la función termina (siempre que no formen parte de un closure)
var MYAPP = {};
MYAPP.dom = (function() {
// initialization code...
function _private() { console.log('_private'); }
// ... body }
return {
getStyle: function(el, prop) {
console.log('getStyle');
_private();
},
setStyle: function(el, prop, value) {
console.log('setStyle');
}
};
})();
Otra manera de crear objetos es ejecutar directamente la función anónima y desde dentro hacer las asignaciones que correspondan. Podemos pasarle parámetros a esta función inmediata (normalmente relacionadas con el entorno) y hacer nuestro código más rápido y robusto
http://markdalgleish.com/2011/03/self-executing-anonymous-functions/
var MYAPP = {};
(function(global, doc, ns, $, _undefined_){
console.log ( window === global);
console.log ( document === doc );
console.log ( ns === MYAPP );
console.log ( $ === jQuery );
ns.existClass = (function () {
if (doc.getElementsByClassName) {
return function(sClassName) {
return doc.getElementsByClassName(sClassName).length > 0;
}
}
else {
return function(sClassName) {
return $("*").hasClass(sClassName);
}
}
})();
})(window, document, MYAPP, jQuery);
MYAPP.existClass('hidden');
MYAPP.existClass('___hidden____');
... si te estas preguntando para qué se define el parámetro undefined prueba esto en Safari:
http://javascriptweblog.wordpress.com/2010/08/16/understanding-undefined-and-preventing-referenceerrors/
http://docs.jquery.com/JQuery_Core_Style_Guidelines#Type_Checks
undefined = 5;
(function( _undefined_ ){
console.log ( undefined === _undefined_ );
console.log ( typeof(undefined) === "undefined" );
console.log ( typeof(_undefined_) === "undefined" );
console.log ( myVar === undefined );
console.log ( myVar === _undefined_ ); // this is faster
console.log ( typeof(myVar) === "undefined" );
})();
[performance]
http://addyosmani.com/blog/faster-javascript-memoization/
http://philogb.github.com/blog/2008/09/05/memoization-in-javascript/
- Esta técnica se utiliza para cachear resultados de operaciones costosas.
- Si no se ha realizado aun la operación, se realiza y se guarda en caché (objeto o array) con un identificador único
- Si ya se ha realizado, se devuelve el resultado directamente de la caché
var hasClassName = (function(){
var cache = { };
return function(element, className) {
var re = (cache[className] ||
(cache[className] = new RegExp('(?:^|\\s)' + className + '(?:\\s|$)') ));
return re.test(element.className);
};
})();
[creación de objetos]
http://www.yuiblog.com/blog/2007/06/12/module-pattern/
- La idea del patrón modulo es la de encapsular la lógica privada y exponer solamente de terminados métodos “público”. Se aplica utilizando funciones autoejecutables que devuelven objetos
var functionUtils = (function(){
/* private `slice` method */
function slice(arrayLike, index) {
return Array.prototype.slice.call(arrayLike, index || 0);
}
return {
/* exposed, public `bind` method */
bind: function(fn, thisArg) {
var args = slice(arguments, 2);
return function() {
return fn.apply(thisArg, args.concat(slice(arguments)));
};
}
};
})();
[creación de objetos]
https://github.com/shichuan/javascript-patterns/blob/master/object-creation-patterns/sandbox.html
-
El patrón Sandbox soluciona dos problemas que hay con el patrón Namespce :
- No se pueden crear 2 versiones de la misma aplicación en la misma página ya que todo cuelga del mismo objeto global (
MYAPP
) - Largas rutas a resolver (y que escribir) para acceder a los metodos (`MYAPP.utilities.array)
- No se pueden crear 2 versiones de la misma aplicación en la misma página ya que todo cuelga del mismo objeto global (
-
El patrón Sandbox utiliza una función constructora global, en vez de un objeto “estático” global.
-
Al pasarle una función
fpCallback
a este constructor, se creará un objeto (box) donde esta funciónfpCallback
tendrá disponible los metodos que necesita.
function Sandbox() {
// turning arguments into an array
var args = Array.prototype.slice.call(arguments),
// the last argument is the callback
callback = args.pop(),
// modules can be passed as an array or as individual parameters
modules = (args[0] && typeof args[0] === "string") ? args : args[0], i;
// make sure the function is called
// as a constructor
if (!(this instanceof Sandbox)) {
return new Sandbox(modules, callback);
}
// add properties to `this` as needed:
this.a = 1;
this.b = 2;
// now add modules to the core `this` object
// no modules or "*" both mean "use all modules"
if (!modules || modules == '*') {
modules = [];
for (i in Sandbox.modules) {
if (Sandbox.modules.hasOwnProperty(i)) {
modules.push(i);
}
}
}
// initialize the required modules
for (i = 0; i < modules.length; i += 1) {
Sandbox.modules[modules[i]](this);
}
// call the callback
callback(this);
}
// any prototype properties as needed
Sandbox.prototype = {
name:"My Application",
version:"1.0",
getName:function () {
return this.name;
}
};
Sandbox.modules = {};
Sandbox.modules.dom = function (box) {
box.getElement = function () {};
box.getStyle = function () {};
box.foo = "bar";
};
Sandbox.modules.event = function (box) {
// access to the Sandbox prototype if needed:
// box.constructor.prototype.m = "mmm";
box.attachEvent = function () {};
box.detachEvent = function () {};
};
Sandbox.modules.ajax = function (box) {
box.makeRequest = function () {};
box.getResponse = function () {};
};
// how to use
Sandbox(['ajax', 'event'], function (box) {
console.log(box);
});
Sandbox('ajax', 'dom', function (box) {
console.log(box);
});
Sandbox('*', function (box) {
console.log(box);
});