Buenas prácticas en JavaScript
- Artículo anterior—El primer vistazo a JavaScript
- Siguiente artículo—Los principios de un JavaScript discreto
- Tabla de contenidos
Introducción
El escribir un artículo de buenas prácticas es un negocio complicado. Para un buen número de gente, lo que se puede leer a continuación será bastante obvio y justo lo que hay que hacer.
De cualquier forma, echando un vistazo sobre la web, y teniendo código entregado a mi por otros desarrolladores durante años, creo que el sentido común es, en realidad, una rareza en el código disponible en internet. Y que “lo razonable y lógico de hacer” se oculta en el fondo de la lista de prioridades una vez se está en dentro de un proyecto, y la fecha límite se acerca.
Así que he decidido hacer esto más fácil, creando este artículo, que es una recopilación de buenas prácticas y sentido común que he generado durante años, muchas de ellas aprendidas de la forma dificil (experimentación y demás). Hay que tomar esa advertencia de arriba, y tenerla a mano, para poder aplicarla casi sin tener que pensar en ella. Seguro que hay cosas con las que se discrepa, es una buena cosa - hay que cuestionar lo que se lee, para encontrar mejores soluciones. De todas formas, creo que seguir estos principios me ha hecho un desarrollador más efectivo, y permitido que otros desarrolladores construyan sobre mi trabajo de forma más sencilla.
El artículo se estructura como sigue:
- Llamar a las cosas por su nombre — nombres de variables y funciones fáciles, cortos y leibles
- Evitar globales
- Mantenerse en un estilo de codificación estricta
- Comenta tanto como necesites, pero no más
- Evitar mezclas con otras tecnologias
- Utilizar notaciones cortas cuando tenga sentido
- Modulariza — una función por tarea
- Mejoras progresivas
- Permitir configuraciones y traducciones
- Evitar anidamientos excesivos
- Optimización de bucles
- Mantener los accesos al árbol DOM al mínimo
- No ceder a los caprichos de los navegadores
- No confiar en ningún dato
- Añadir funcionalidad con JavaScript, no crear demasiado contenido
- Construir sobre los hombros de gigantes
- Código de desarrollo no es código vivo
Llamar a las cosas por su nombre — nombres de variables y funciones fáciles, cortos y leibles
No tiene sentido, y asusta un poco, lo frecuente que es encontrarse con variables como x1
,
fe2
ó xbqne
en JavaScript, ó — en el otro lado del espectro —
de los nombres de variables como incrementorForMainLoopWhichSpansFromTenToTwenty
ó
createNewMemberIfAgeOverTwentyOneAndMoonIsFull
.
Ninguno de esos tiene mucho sentido — los buenos nombres de variables y funciones deberian ser fáciles
de entender, y decirnos de que tratan—, ni más, ni menos. Una trampa a evitar es casar los valores y la
funcionalidad en los nombres. Una función llamada isLegalDrinkingAge()
tiene más sentido que
isOverEighteen()
, ya que la edad legal para beber varía de país a país, y hay más cosas que el
beber que están limitadas por edad.
La notación Húngara es un buen esquema de nombrado de variables a adoptar (hay otros esquemas de nombrado a considerar), la ventaja es que sabemos lo que algo se supone qué es, y no sólo lo qué es.
Por ejemplo, si tenemos una variable llamada familyName
, y se supone que es una cadena, podríamos
haberla escrito como sFamilyName
en “notación Húngara”. Un objeto llamado member
podría ser oMember
y un booleano llamado isLegal
podría ser bIsLegal
.
Es muy informativo para algunos, pero es algo demasiado para otros — queda en cada uno decidir si se
utiliza ó no.
Manteniendo los nombres en inglés es también una buena idea, también. Los lenguajes de programación están en inglés, así que porqué no mantener esto como un paso lógico para el resto del código. Habiendo pasado algún tiempo depurando código en Coreano y Eslovaco, puedo asegurar que no es muy gracioso para un hablante no nativo.
Hay que mirar al código como a una narrativa. Si podemos leer línea por línea, y entender de qué va, perfecto. Si necesitamos utilizar un bloc de dibujo para seguir el flujo de la lógica, entonces ese código necesita algún trabajo. Se puede intentar leer Dostojewski si queremos una comparación con el mundo real — yo me perdí en una página con 12 nombres rusos, cuatro de los cuales eran seudónimos. No hay que escribir código como ese — es más un arte que un producto, esto raramente es una buena cosa.
Evitar globales
Variables y nombres de funciones globales son una mala idea increible. La razón para esto es que cada fichero JavaScript incluido en una página funciona en el mismo ámbito. Si tenemos variables y funciones globales en nuestro código, los scripts incluidos después puede contener las mismas variables y nombres de funciones, que sobreescribirán nuestras variables o funciones.
Hay varios apaños para evitar utilizar globales — los veremos uno a uno ahora. Digamos que tenemos tres funciones y una variable como éstas:
var current = null;
function init(){...}
function change(){...}
function verify(){...}
Podemos protegerlas de ser sobreescritas utilizando un objeto literal:
var myNameSpace = {
current:null,
init:function(){...},
change:function(){...},
verify:function(){...}
}
Esto funciona, pero tiene un inconveniente — para llamar a las funciones, o cambiar los valores de la variable
necesitamos siempre ir a través del nombre del objeto principal: init()
es ahora myNameSpace.init()
,
current es myNameSpace.current
y así. Esto puede resultar molesto y repetitivo.
Es mucho más sencilo envolver todo en una función anónima, y proteger el ámbito de esa forma. Eso también significa que
no tenemos que cambiar de sintaxis, desde function name()
a name:function()
. Este truco se llama
Patrón Módulo (Module Pattern):
myNameSpace = function(){
var current = null;
function init(){...}
function change(){...}
function verify(){...}
}();
De nuevo, este enfoque no viene sin incovenientes. Ninguno de estas cosas está disponible fuera de la función en absoluto.
Si deseamos hacerlas disponible, tenemos que envolver esas cosas en una sentencia return
:
myNameSpace = function(){
var current = null;
function verify(){...}
return{
init:function(){...}
change:function(){...}
}
}();
Esto nos lleva de nuevo al punto de partida, en cuanto a vincular de una forma a la otra, y al cambio de sintaxis. Es preferible hacer algo como lo siguiente (que he llamado “revealing module pattern”):
myNameSpace = function(){
var current = null;
function init(){...}
function change(){...}
function verify(){...}
return{
init:init,
change:change
}
}();
En vez de devolver las propiedades y métodos, justamente devolvemos punteros a ellas. Esto hace mas sencullo llamar
a funciones, y acceder a variables, desde otros lugares sin tener que ir a través del nombre myNameSpace
.
Esto también significa que podemos tener un alias público para una función, en caso de que queramos tener un nombre más largo o descriptivo de uso interno, pero uno más corto para afuera.:
myNameSpace = function(){
var current = null;
function init(){...}
function change(){...}
function verify(){...}
return{
init:init,
set:change
}
}();
Llamando a myNameSpace.set()
se invocará el método change()
.
Si no necesitamos que ninguna de las variables o funciones estén disponibles fuera, simplemente envolvemos toda la construcción en otro conjunto de paréntesis, para ejecutarlo sin tener que asignar un nombre a ello:
(function(){
var current = null;
function init(){...}
function change(){...}
function verify(){...}
})();
Esto mantiene todo en un paquete pequeño y ordenado, que es inacesible para el mundo exterior, pero que es muy fácil compartir variables y funciones dentro de él.
Mantenerse en un estilo de codificación estricta
Los navegadores son muy olvidadizos cuando interpretan la sintaxis de JavaScript. Esto no debería ser una razón para escribir código descuidado que dependa de los navegadores para funcionar.
La forma más sencilla de comprobar la calidad sintáctica de nuestro código es pasarlo a través de JSLint — una herramienta de validación de JavaScript, que nos da informe detallado sobre warnings en la sintaxis, y su significado. Determinada gente ha escrito extensiones para editores (por ejemplo, JS Tools for TextMate) que, automáticamente, chequean el código cuando lo guardamos.
JSLint puede ser un poco quisquilloso con los resultados que devuelve, y — como dice su desarrollador Douglas Crockford — puede herir nuestros sentimientos.Yo puedo decir que escribo mucho mejor código desde que instalé la heramienta TextMate JS, y y empezé a someter el código al escrutinio de JSLint.
Código válido y limpio significa menos bugs confusos a resolver, mejor integración con otros desarrolladores, y mejor seguridad en el código. Cuando confiamos en hacks para hacer que nuestro código funcione, es posible que exista algún exploit que utilize esos mismos hacks. Además, según los hacks se van arreglando en los navegadores, nuestro código dejará de funcionar en subsiguientes versiones del navegador.
Código válido también significa que puede ser convertido por scripts a otros formatos — código basado en hacks necesitará de un humano para hacerlo.
Comenta tanto como necesites, pero no más
Los comentarios suelen ser mensajes a otros desarrolladores (y a nosotros mismos, si regresamos a nuestro código después de un tiempo trabajando en otras cosas). Hay numerosas batallas, con discusiones acaloradas durante años, sobre cómo usar los comentarios, el principal argumento sigue siendo que un buen código debe autoexplicarse.
Lo que veo como defecto en este argumente es que las explicaciones son una cosa muy subjetiva — no puedes esperar que cada desarrollador entienda que hace un determinado código partiendo de la misma explicación.
Los comentarios no van a herir a nadie si hacemos las cosas bien. Volveremos sobre esto en el último punto de este artículo, pero digamos que si nuestros comentarios terminan en el código que los usuarios finales ven, entonces algo no está yendo bien.
Una vez más, el truco es la moderación. Hay que comentar cuando hay una cosa importante que decir. Y si
se comenta, es mejor usar la notación /* */
. Los comentarios de una única línea utilizando
//
pueden ser problemáticos si la gente minimiza nuestro código sin eliminar los comentarios,
además de que suele ser menos versátil.
Si necesitamos comentar determinadas partes de código para utilizarlas más tarde, o para depurar, hay un truco muy fácil que podemos usar:
module = function(){
var current = null;
function init(){
};
/*
function show(){
current = 1;
};
function hide(){
show();
};
*/
return{init:init,show:show,current:current}
}();
Si se añade un doble barra delante del */ de cierre del comentario, se puede comentar y descomentar todo el bloque simplemente quitando o poniendo una barra delante del /* del inicio del comentario:
module = function(){
var current = null;
function init(){
};
/*
function show(){
current = 1;
};
function hide(){
show();
};
// */
return{init:init,show:show,current:current}
}();
Con el código según está en el bloque de arriba, se se añade una / delante del /*, hará que el comentario que abarca varias líneas se convierta en dos comentarios de una línea cada uno, “descomentando” todo el código entre medias y haciendo que se ejecute. Quitando esa / el comentario volverá a ocultar todo el bloque.
Para grandes aplicaciones, la documentación de comentarios en JavaDoc style tiene mucho sentido — se planta la semilla de la documentación de un producto escribiendo código. El éxito de Yahoo User Interface Library es en parte atribuible a esto, e incluso hay una herramiento que nos permite construir la misma documentación para nuestros productos. No hay que preocuparse mucho acerca de esto hasta que estemos mas experimentados con JavaScript — JavaDoc se menciona para completar.
Evitar mezclas con otras tecnologias
Aunque sea posible crear todo lo que necesitemos en una página web utilizando JavaScript y DOM, no es necesariamente la forma más efectiva de hacerlo. El código siguiente pone un borde rojo sobre cada campo de entrada en un formulario, mientras que su clase es “obligatoria” y no hay nada en ella.
var f = document.getElementById('mainform');
var inputs = f.getElementsByTagName('input');
for(var i=0,j=inputs.length;i<j;i++){
if(inputs[i].className === 'mandatory' &&
inputs[i].value === ''){
inputs[i].style.borderColor = '#f00';
inputs[i].style.borderStyle = 'solid';
inputs[i].style.borderWidth = '1px';
}
}
Esto funciona, pero desde luego, si necesitamos hacer cambios más tarde a estos estilos, tenemos que ir a través de todo el código JavaScript, y aplicar los cambios ahí. Cuanto más complejo es es cambio, mas complejo es la modificación. Además, no todos los desarrolladores JavaScript es competente o interesado en CSS, lo que significa que habrá un montón de idas y venidas hasta que la salida correcta se consiga. Si añadimos una class llamada “error” al elemento cuando éste sea un error, podemos asegurarnos que toda la información se mantiene dentro del CSS, que es más apropiado:
var f = document.getElementById('mainform');
var inputs = f.getElementsByTagName('input');
for(var i=0,j=inputs.length;i<j;i++){
if(inputs[i].className === 'mandatory' &&
inputs[i].value === ''){
inputs[i].className += ' error';
}
}
Esto es mucho más eficiente, ya que CSS se pensó para aplicarse en cascada por todo el documento. Decir,
por ejemplo, que necesitamos ocultar todos los DIV con una cierta class en un documento. Podríamos buscar todos
los DIV, chequar su class, y entonces cambiar su estilo. En los navegadores más modernos podemos utilizar un
motor de selección sobre CSS, y entonces alterar el estilo. De todas formas, las forma más sencilla es utilizar
JavaScript para poner un class en el elemento padre, y utilizar la sintaxis a lo largo de las lineas de
element.triggerclass div.selectorclass{}
en las CSS. Hay que dejar el trabajo de ocultar los DIV
al diseñador de las CSS, ya que él sabrá cuál es la mejor forma para hacerlo.
Utilizar notaciones cortas cuando tenga sentido
Las notaciones cortas es un truco: por una parte nos permite tener código más pequeño, pero por otra parte se hace más duro para los desarrolladores que parten de nuestro código, ya que pueden no conocer los accesos directos.
Los objetos son, probablemente, la cosa más versátil que hay en JavaScript. La forma de la vieja escuela era escribirlos más ó menos así:
var cow = new Object();
cow.colour = 'brown';
cow.commonQuestion = 'What now?';
cow.moo = function(){
console.log('moo');
}
cow.feet = 4;
cow.accordingToLarson = 'will take over the world';
No obstante, esto implica que hay que repetir el nombre del objeto para cada propiedad ó nombre, que puede llegar a ser molesto. En vez de eso, tiene mucho más sentido tener la siguiente construcción, también llamada objeto literal:
var cow = {
colour:'brown',
commonQuestion:'What now?',
moo:function(){
console.log('moo);
},
feet:4,
accordingToLarson:'will take over the world'
};
Los arrays son un punto confuso en JavaScript. Se pueden encontrar un montón de scripts definiendo un Array de la siguiente forma: </pp>
var aweSomeBands = new Array();
aweSomeBands[0] = 'Bad Religion';
aweSomeBands[1] = 'Dropkick Murphys';
aweSomeBands[2] = 'Flogging Molly';
aweSomeBands[3] = 'Red Hot Chili Peppers';
aweSomeBands[4] = 'Pornophonique';
Aquí se pierde mucho por repetición, se puede escribir mejor y más rápido utilizando los [ ]
:
var aweSomeBands = [
'Bad Religion',
'Dropkick Murphys',
'Flogging Molly',
'Red Hot Chili Peppers',
'Pornophonique'
];
En alguno tutoriales se ve el término “arrays asociativos”. Esto es un nombre equivocado, ya que los arrays con propiedades en vez de índices son objetos actualmente, y deberian ser definidos como tal.
Las condiciones se pueden acortar utilizando la “notacion ternaria”. Por ejemplo, el siguiente código define una variable como 1 ó -1, dependiendo del valor de otra variable.
var direction;
if(x > 100){
direction = 1;
} else {
direction = -1;
}
Esto se puede acortar a una única línea:
var direction = (x > 100) ? 1 : -1;
Cualquier cosa antes de la interrogación es la condición; el valor inmediatamente después es el caso verdadero, y el caso falso es el que va después de los dos puntos. La notación terciaria se puede anidar, pero no es aconsejable si se desea dejar las cosas legibles.
Otra situación común en JavaScript es tener un valor predefinido para una variable si esta no está definida:
if(v){
var x = v;
} else {
var x = 10;
}
Una notación que acorta esto son las dos ||:
var x = v || 10;
Este código da automáticamente a x
el valor 10
si v
no está
definida — así de simple.
Modulariza — una función por tarea
Es una buena práctica en la programación en general — estar seguro de crear funciones que terminan un trabajo, y a la vez que sean fáciles de depurar y cambiar por otros desarrolladores, sin tener que escanear todo el código para saber que trozos de código hacen que función.
Esto también implica crear funciones de ayuda para las tareas más comunes. Si nos encontramos haciendo las mismas cosas en diferentes funciones, entonces, es una buena idea crear una función genérica con ese código, y reutilizarla cuando sea necesario.
Además, tiene más sentido sacar código fuera que utilizar ifs dentro de la propia función. Por ejemplo, digamos que queremos escribir una función de ayuda para crear nuevos enlaces. Podríamos hacerlo así:
function addLink(text,url,parentElement){
var newLink = document.createElement('a');
newLink.setAttribute('href',url);
newLink.appendChild(document.createTextNode(text));
parentElement.appendChild(newLink);
}
Esto funciona, pero podemos encontrarnos teniendo que añadir diferentes atributos dependiendo de qué elementos queremos aplicar al enlace. Por ejemplo:
function addLink(text,url,parentElement){
var newLink = document.createElement('a');
newLink.setAttribute('href',url);
newLink.appendChild(document.createTextNode(text));
if(parentElement.id === 'menu'){
newLink.className = 'menu-item';
}
if(url.indexOf('mailto:')!==-1){
newLink.className = 'mail';
}
parentElement.appendChild(newLink);
}
Esto hace que la función sea más específica y difícil de aplicar de diferentes situaciones. Una alternativa
más sencilla es devolver el enlace y recubrir los casos extras en las funciones en las que se necesiten.
Esto convierte a addLink()
en algo más genérico, createLink()
:
function createLink(text,url){
var newLink = document.createElement('a');
newLink.setAttribute('href',url);
newLink.appendChild(document.createTextNode(text));
return newLink;
}
function createMenu(){
var menu = document.getElementById('menu');
var items = [
{t:'Home',u:'index.html'},
{t:'Sales',u:'sales.html'},
{t:'Contact',u:'contact.html'}
];
for(var i=0;i<items.length;i++){
var item = createLink(items.t,items.u);
item.className = 'menu-item';
menu.appendChild(item);
}
}
Teniendo que todas nuestras funciones hacen sólo una cosa, podemos tener una funcion principal,
init()
, para nuestra aplicación, que contenga toda la estructura de la aplicación. De esta forma
podemos cambiar fácilmente nuestra aplicación, y quitar funcionalidad sin tener que recorrer el código para
ver las dependencias.
Mejoras progresivas
La Mejora Progresiva es una forma de desarrollo que se explica al detalle en Degradación elegante frente a mejora progresiva. En esencia, lo que deberíamos hacer es escribir código que funcione independientemente de la tecnología disponible. En el caso de JavaScript, esto significa que cuando no está disponible (por ejemplo sobre BlackBerry, ó quizás por alguna política de seguridad), nuestras páginas web deberían permitir a los usuarios alcanzar un determinado objetivo, pero no bloquearlos por la falta de JavaScript, que pueden querer tener activo ó no.
Es asombroso cuantas veces construímos soluciones que incorporan grandes cantidades de código JavaScript (bastante enrevesado encima) para problemas que se pueden resolver fácilmente sin el. Un ejemplo, un cuadro de búsqueda en una página que permitía buscar diferentes datos: web, imágenes, news, etc.
En la versión original, las diferentes opciones de búsquedas eran enlaces a los que se sobreescribía
el atributo action
del formulario, para que apuntara a diferentes scripts en la parte del servidor
para hacer las búsquedas.
El problema era que si JavaScript estaba deshabilitado, los enlaces se seguían mostrando, pero cada búsqueda retornaba los resultados de búsqueda sobre la web, ya que la acción del formulario no se cambiaba nunca. La solución era bastante simple: en vez de los enlaces, se mostraban las opciones como radio buttons, y se hacía el cambio en las diferentes formas de búsquedas utilizando un script en la parte servidora.
Esto no solamente hace que la búsqueda sea correcta para cualquiera, sino que además hace sencillo el conocer cuantos usuarios utilizan qué opciones. Mediante la elección de la construcción HTML correcta, logramos deshacernos tanto del código JavaScript para cambiar la acción del formulario, como de los scripts de cambio de búsquedas, y se hace que funcione para cada usuario que esté ahí afuera — independiente del entorno.
Permitir configuraciones y traducciones
Una de las mejores cosas para mantener nuestro código mantenible y limpio es crear un objeto de configuración que contenga todas aquellas cosas que se pueden cambiar con el tiempo. Esto incluye cualquier texto usado en los elementos que creamos (incluyendo valores de botones y texto alternativo para imágenes), clases CSS, nombres de ID, y parámetros en general del interfaz que estamos construyendo.
Por ejemplo, Easy YouTube player tiene el siguiente objeto de configuración:
/*
This is the configuration of the player. Most likely you will
never have to change anything here, but it is good to be able
to, isn't it?
*/
config = {
CSS:{
/*
IDs used in the document. The script will get access to
the different elements of the player with these IDs, so
if you change them in the HTML below, make sure to also
change the name here!
*/
IDs:{
container:'eytp-maincontainer',
canvas:'eytp-playercanvas',
player:'eytp-player',
controls:'eytp-controls',
volumeField:'eytp-volume',
volumeBar:'eytp-volumebar',
playerForm:'eytp-playerform',
urlField:'eytp-url',
sizeControl:'eytp-sizecontrol',
searchField:'eytp-searchfield',
searchForm:'eytp-search',
searchOutput:'eytp-searchoutput'
/*
Notice there should never be a comma after the last
entry in the list as otherwise MSIE will throw a fit!
*/
},
/*
These are the names of the CSS classes, the player adds
dynamically to thevolume bar in certain
situations.
*/
classes:{
maxvolume:'maxed',
disabled:'disabled'
/*
Notice there should never be a comma after the last
entry in the list as otherwise MSIE will throw a fit!
*/
}
},
/*
That is the end of the CSS definitions, from here on
you can change settings of the player itself.
*/
application:{
/*
The YouTube API base URL. This changed during development og this,
so I thought it useful to make it a parameter.
*/
youtubeAPI:'http://gdata.youtube.com/apiplayer/cl.swf',
/*
The YouTube Developer key,
please replace this with your own when you host the player!!!!!
*/
devkey:'AI39si7d...Y9fu_cQ',
/*
The volume increase/decrease in percent and the volume message
shown in a hidden form field (for screen readers). The $x in the
message will be replaced with the real value.
*/
volumeChange:10,
volumeMessage:'volume $x percent',
/*
Amount of search results and the error message should there
be no reults.
*/
searchResults:6,
loadingMessage:'Searching, please wait',
noVideosFoundMessage:'No videos found : (',
/*
Amount of seconds to repeat when the user hits the rewind
button.
*/
secondsToRepeat:10,
/*
Movie dimensions.
*/
movieWidth:400,
movieHeight:300
/*
Notice there should never be a comma after the last
entry in the list as otherwise MSIE will throw a fit!
*/
}
}
Si tenemos esto como una parte un módulo, y deseamos hacerlo público, tenemos que permitir a los desarrolladores el sobreescribir sólamente aquello que necesitan antes de inicializar nuestro módulo.
Es de suma importancia mantener nuestro código simple, evitando la necesidad, para futuros mantenedores de él, el tener que leer todo nuestro código para encontrar donde necesitan hacer un cambio. Si esto no es obvio, la solución será abandonar ese código ó hackearlo. Las soluciones hackeadas son imposibles de parchear si necesitamos actualizarlas, e impiden la reutilización de código.
Evitar anidamientos excesivos
Los anidamientos en el código explican mejor su lógica, y hacen mucho más sencillo su lectura, pero excesivos anidamientos pueden ser difíciles de seguir. Los lectores de nuestro código no deberían tener que hacer scroll horizontal, o estarán bastante confundidos si sus editores tiene que partir las líneas largas (esto hace discutible nuestros esfuerzos por sangrar el código).
El otro problema con el anidamiento son los nombres de variables y bucles. Como normalmente empezamos
nuestro primer bucle con la variable i
, después tendremos que seguir con j, k, l, y así.
Esto se hace confuso rápidamente:
function renderProfiles(o){
var out = document.getElementById(‘profiles’);
for(var i=0;i<o.members.length;i++){
var ul = document.createElement(‘ul’);
var li = document.createElement(‘li’);
li.appendChild(document.createTextNode(o.members[i].name));
var nestedul = document.createElement(‘ul’);
for(var j=0;j<o.members[i].data.length;j++){
var datali = document.createElement(‘li’);
datali.appendChild(
document.createTextNode(
o.members[i].data[j].label + ‘ ‘ +
o.members[i].data[j].value
)
);
nestedul.appendChild(datali);
}
li.appendChild(nestedul);
}
out.appendChild(ul);
}
Como hemos usado nombres génericos de variables — de usar y tirar — como ul
y
li
, pues necesitamos nestedul
y datali
para los items anidados.
Si la lista anidada fuera más profunda, necesitaría más nombres de variables, y así. Tiene más sentido
poner la tarea de crear listas anidadas para cada miembro en su propia función, y llamarlas con los datos
correctos. Esto previene, además, tener bucles dentro de bucles. La función genérica
addMemberData()
tiene buena pinta, y es probable que la necesitemos en otras ocasiones. Teniendo
esto en cuenta, se podría reescribir el código como sigue:
function renderProfiles(o){
var out = document.getElementById(‘profiles’);
for(var i=0;i<o.members.length;i++){
var ul = document.createElement(‘ul’);
var li = document.createElement(‘li’);
li.appendChild(document.createTextNode(data.members[i].name));
li.appendChild(addMemberData(o.members[i]));
}
out.appendChild(ul);
}
function addMemberData(member){
var ul = document.createElement(‘ul’);
for(var i=0;i<member.data.length;i++){
var li = document.createElement(‘li’);
li.appendChild(
document.createTextNode(
member.data[i].label + ‘ ‘ +
member.data[i].value
)
);
}
ul.appendChild(li);
return ul;
}
Optimización de bucles
Los bucles pueden ser muy lentos si no se hacen bien. Uno de los errores más comunes es leer el atributo length de un array en cada iteración:
var names = ['George','Ringo','Paul','John'];
for(var i=0;i<names.length;i++){
doSomeThingWith(names[i]);
}
Esto significa que en cada iteración del bucle, JavaScript necesita calcular la longitud del array. Se puede evitar esto almacenando la longitud en una variable:
var names = ['George','Ringo','Paul','John'];
var all = names.length;
for(var i=0;i<all;i++){
doSomeThingWith(names[i]);
}
Una forma más corta de hacer esto mismo es crear una segunda variable en la parte previa del bucle:
var names = ['George','Ringo','Paul','John'];
for(var i=0,j=names.length;i<j;i++){
doSomeThingWith(names[i]);
}
Otra cosa a tener en cuenta es que debemos mantener fuera de los bucles código computacionalmente pesado. Esto include expresiones regulares, y, — más importante —, manipulación del árbol DOM. Podemos crear nodos DOM en los bucles, pero evitar insertarlos en el documento. Podemos encontrar más sobre buenas prácticas en los árboles DOM en la siguiente sección.
Mantener los accesos al árbol DOM al mínimo
Acceder al árbol DOM en los navegadores es algo bastante caro. El árbol DOM es una API bastante compleja, y el renderizado puede llevarles bastante tiempo a los navegadores. Se puede ver esto cuando estamos viendo complejas aplicaciones web, y nuestro ordenador está al máximo con otros trabajos — los cambios tardan bastante, ó se pueden mostrar a la mitad mientras se terminan de cargar.
Para asegurarnos de que nuestro código es rápido y no ralentiza al navegador, hay que intentar mantener los accesos al árbol DOM al mínimo. En vez de crear y mostrar elementos constantemente, hay que tener una función que modifique el árbol DOM, y hay que llamar a esa función al final de nuestro proceso de generación, para que el navegador sólo tenga que renderizar una vez, y no de forma contínua.
No ceder a los caprichos de los navegadores
Escribir código específico para un determinado navegador es una vía segura para que nuestro código sea difícil de mantener, y de que se haga obsoleto rápidamente. Si buscamos en la web, encontraremos una cantidad de scripts que funcionaban en un determinado navegador, y dejaban de funcionar tan pronto como una nueva versión del navegador veía la luz.
Esto es perder tiempo y esfuerzo — deberíamos construir código basandonos en standards, cómo los esbozados en estos artículos, pero no solamente para un navegador. La web es para todos, no solamente para un grupo de usuarios de élite, con una configuración determinada. Y ya que el mercado de los navegadores se mueve rápidamente, cada dos por tres nos tocaría arreglar nuestro código. Esto, ni es efectivo, ni gracioso.
Si algo increíble funciona sólo en un navegador, y tenemos que usarlo, deberíamos poner ese código en un fichero específico, y llamarlo con el nombre del navegador y su versión. Esto hace que podamos encontrar y eliminar funcionalidad más fácilmente, si el navegador o su versión se quedan obsoletos.
No confiar en ningún dato
Uno de los puntos principales a tener en cuenta cuando se habla sobre código y seguridad de datos, es no confiar en ningún dato. No es solamente sobre gente malvada que quiere reventar nuestros sistemas; esto comienza con la usabilidad. Los usuario teclearán datos incorrectos todo el rato. No porque sean estúpidos, sino porque están ocupados, distraidos, o porque nuestras instrucciones les confunden. Por ejemplo, acabo de reservar una habitación en un hotel por un mes en vez de por seis dias, por meter un número equivocado … me considero bastante inteligente.
En pocas palabras, aseguremonos que todos los datos que llegan a nuestros sistema son claros y limpios, y
son exactamente los que necesitamos. Esto es muy importante en el backend, cuando se sacan los parametros
de la URL. En JavaScript, es muy importante testear el tipo de los parametros enviados a las funciones
(utilizando typeof
). Lo siguiente podría ser un error si members
no es un Array
(por ejemplo, para una cadena se crearía una lista por cada caracter en la cadena):
function buildMemberList(members){
var all = members.length;
var ul = document.createElement('ul');
for(var i=0;i<all;i++){
var li = document.createElement('li');
li.appendChild(document.createTextNode(members[i].name));
ul.appendChild(li);
}
return ul;
}
Para hacer que esto funcione, habría que chequear el tipo de members
y asegurarnos de
que es un array:
function buildMemberList(members){
if(typeof members === 'object' &&
typeof members.slice === 'function'){
var all = members.length;
var ul = document.createElement('ul');
for(var i=0;i<all;i++){
var li = document.createElement('li');
li.appendChild(document.createTextNode(members[i].name));
ul.appendChild(li);
}
return ul;
}
}
Los arrays son complicados, porque nos dicen que son objetos. Para asegurarnos, chequeamos uno de los métodos que sólo tienen los arrays.
Otra práctica muy insegura es leer información del árbol DOM, y utilizarla sin más comprobaciones. Por ejemplo, una vez tuve que depurar un código que provocaba que la funcionalidad ofrecida mediante JavaScript fallase. El código que causaba esto — por razones desconocidas — leía un nombre de usuario del innerHTML de un elemento de la página, y llamaba a una función con ese dato como parámetro. Como el nombre de usuario podía ser cualquier carácter UTF-8, podía incluir simples y dobles comillas, terminando cualquier cadena y considerando el resto como datos erróneos. Además. cualquier usuario que cambiase el HTML utilizando herramientas como Firebug ó Opera Dragon Fly podría cambiar el nombre de usuario, e inyectar ese nombre en nuestras funciones.
Lo mismo aplica a las formas validación únicamente en el lado cliente. Una vez me suscribí con una dirección de correo electrónico inexistente, reescribiendo una select para utilizar otra opción. Como el formulario no se chequeaba en la parte servidora, el proceso finalizó correctamente, sin problemas.
Para los accesos DOM, chequear que el elemento que intentamos encontrar y alterar está realmente disponible, y es lo que nosotros esperamos — de otra forma el código podría fallar, o provocar errores en el renderizado.
Añadir funcionalidad con JavaScript, no crear demasiado contenido
Como se ha podido ver en algunos de los ejemplos, construir una gran cantidad de HTML en JavaScript puede
ser una tarea de enormes proporciones, incluso complicada. Especialmente en Internet Explorer, donde podemos
tener todos tipo de problemas alterando el documento mientras se está cargando la página, o manipulando el
contenido (echar un vistazo a “operation aborted error” en
Google para una poca de miseria) con innerHTML
.
En términos del mantenimiento de la página es una idea horriblemente mala el crear una gran cantidad de marcas HTML, ya que no todos los posibles mantenedores pueden tener el nivel necesario para entenderlo, cosa que podría ensuciar nuestro código.
Me he dado cuenta que cuando tengo que construir una aplicación que es muy dependiente de JavaScript, utilizar una plantilla HTML, y cargar esa plantilla vía Ajax, es algo que tiene mucho más sentido. De esa forma, los mantenedores pueden alterar la estructura HTML, y los textos más importantes sin tener que interferir con nuestro código JavaScript. La única pega es que hay que decirles qué IDs se necesitan, y si hay ciertas construcciones HTML que tienen que estar en el orden que hemos definido. Esto lo podemos hacer con comentarios HTML (y luego eliminar los comentarios cuando cargamos la plantilla. Ver el código de Easy YouTube template para ver un ejemplo.
En el script, se carga la plantilla cuando el contenedor HTML adecuado está disponible, y se asigna el
evento handlers al final del método setupPlayer()
:
var playercontainer = document.getElementById('easyyoutubeplayer');
if(playercontainer){
ajax('template.html');
};
function ajax(url){
var request;
try{
request = new XMLHttpRequest();
}catch(error){
try{
request = new ActiveXObject("Microsoft.XMLHTTP");
}catch(error){
return true;
}
}
request.open('get',url,true);
request.onreadystatechange = function(){
if(request.readyState == 4){
if(request.status){
if(request.status === 200 || request.status === 304){
if(url === 'template.html'){
setupPlayer(request.responseText);
}
}
}else{
alert('Error: Could not find template...');
}
}
};
request.setRequestHeader('If-Modified-Since','Wed, 05 Apr 2006 00:00:00 GMT');
request.send(null);
};
De esta forma, se permite a la gente traducir y cambiar el player de la forma que deseen, sin tener que alterar el código JavaScript.
Construir sobre los hombros de gigantes
No es ninguna mentira que durante los últimos años, las librerías y frameworks de JavaScript han copado el mercado del desarrollo web. Y no es mala cosa — si se utilizan de forma correcta. Las buenas librerías de JavaScript desean hacer una cosa y sólo una cosa: hacer mas sencilla la vida de los desarrolladores, bordeando las inconsistencias de los navegadores, y parcheando determinados problemas de éstos. Las librerías de JavaScript nos proveen de una línea base de funcionalidad sobre la que construir.
Es una buena idea aprender inicialmente JavaScript sin librerías, para que podemaos saber cómo van las cosas, pero deberemos hacer uso de las librerías JavaScript cuando desarrollemos sitios web. Habrá menos problemas con los que lidiar, y al menos, los fallos que aparezcan, se podrán reproducir — no serán fallos aleatorios de los navegadores.
Personalmente, mi librería favorita es Yahoo User Interface library (YUI), seguida de jQuery, Dojo y Prototype, pero hay otras docenas de buenas librerías ahi afuera, entre las que debemos encontrar la que mejora nuestros productos.
Aunque todas las librerías trabajan conjuntamente bien, no es una buena idea utilizar varias de ellas en el mismo proyecto. Esto sólo nos da un nivel superfluo de complejidad y de mantenimiento.
Código de desarrollo no es código vivo
Este último punto no es acerca de JavaScript en si, sino de cómo encaja en el resto de la estrategia de desarrollo. Como cualquier cambio en JavaScript tiene un efecto inmediato en el rendimiento y en la funcionalidad de nuestras aplicaciones web, es muy tentador optimizar nuestro código sin tener en cuenta posibles consecuencias para el mantenimiento.
Hay una gran cantidad de trucos bastante inteligentes que se pueden aplicar a JavaScript para mejorar su rendimiento. Pero muchos de ellos vienen con el inconveniente de que nuestro código va a ser más complicado de entender y modificar.
Para escribir un código JavaScript seguro y funcional, tenemos que romper ese ciclo, y dejar de optimizar el código para las máquinas, en vez de para desarrolladores. Además — es algo que es muy común en otros lenguajes, pero no muy conocido entre desarrolladores JavaScript. Un script puede quitar espacios en blanco, comentarios, reemplazar cadenas con Arrays (para evitar que MSIE cree objetos string para cada instancia de una cadena — incluso en condiciones) y hacer otros ajustes más pequeños para que nuestro código JavaScript vuele en los navegadores.
Si nos concentramos en hacer que el código inicial sea fácil de entender y/o modificar por otros desarrolladores, podemos crear el script perfecto. Si necesitamos optimizar prematuramente, nunca llegaremos a eso. Nunca debemos desarrollar para nosotros ó para el navegador — desarrollemos para el siguiente desarrollador que lo coja donde nosotros lo dejemos.
Resúmen
El truco principal con JavaScript es evitar coger el camino fácil. JavaScript es un lenguaje muy versátil, y en el entorno en que es ejecutado es muy fácil escribir código descuidado que parece que hace el trabajo. Ese mismo código, en unos meses quizás, regresará para mordernos unas líneas más abajo.
El desarrollo en JavaScript ha mutado desde un área de conocimiento a una necesidad absoluta si queremos tener un trabajo de desarrollador de páginas web. Si estás comenzando ahora, estás de suerte, ya que yo mismo y otros ya hemos visto la mayoría de los fallos, y hecho toda la parte de prueba-error; ahora podemos mandar ese conocimiento lejos.
- Artículo anterior—El primer vistazo a JavaScript
- Siguiente artículo—Los principios de un JavaScript discreto
- Tabla de contenidos
Sobre el autor
Créditos: Bluesmoon
Chris Heilmann ha sido un desarrollador web durante 10 años, después de chapotear en el periodismo de radio. El trabaja para Yahoo! en UK, como formador y desarrollador principal, y supervisa la calidad del código en Europa y Asia.
Tiene un blog en Wait till I come y está disponible en varias redes sociales, como “codepo8”.
This article is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 Unported license.
Comments
The forum archive of this article is still available on My Opera.