Desde hace dos meses, he trabajado mucho con Javascript y he aprendido mucho sobre este lenguaje tan flexible. He decidido realizar un resumen de las características que considero más importantes y las que diferencian a Javascript del resto de lenguajes de programación que conozco. A mí me sirve para resumir lo aprendido, y espero que a alguien le sirva para acercarse un poco más a este lenguaje.

Javascript es un “dialecto” de ECMAScript

Históricamente, el lenguaje comenzó llamandose Javascript (en el camino lo renombraron a LiveScript, aunque finalmente volvieron al nombre original). En ese entonces comenzaron a surgir otros dialectos con algunas diferencias (como JScript). Sin embargo, en 1997 el lenguaje fue adoptado como un estándar de la asociación ECMA (European Computer Manufacturers Association). A partir de entonces, el nombre del lenguaje que se ajustaba al estándar fue nombrado ECMAScript. En el momento en el que escribí este artículo, ECMAScript se encuentra en su versión 5.1.

A pesar de que siguen existiendo numerosas implementaciones de ECMAScript, normalmente se suelen referir a todas ellas, en general, como Javascript. Todas las implementaciones se ajustan al estándar ECMAScript en el que se basan, y se suelen diferenciar las unas de las otras en cuanto a qué cosas dan soporte y a cuales no. En general, en los proyectos que he trabajado, he podido ignorar las diferencias entre una implementación y otra, ya que todas dan soporte para todo lo que normalmente puedas utilizar. En cualquier caso, te dejo la tarea de informarte de si todas las implementaciones soportan las partes que vayas a utilizar del lenguaje.

(Casi) todo es un objeto

Simplificando: casi todo es un objeto, y lo que no lo es se puede convertir a uno. Hay pocos tipos primitivos, y excepto por algunos casos concretos, el interprete suele convertir los tipos primitivos a objetos cuando intentamos acceder a sus métodos y propiedades.

var cadena = "hola que tal";
console.log(typeof cadena); // string
// Las cadenas wrapean en un objeto String directamente y así se
// puede acceder a su propiedad length (numero de caracteres en la cadena)
console.log("hola que tal".length); // 12
console.log(cadena.length); // 12
console.log(new String("hola que tal").length); // 12

var numero = 3;
console.log(typeof numero); // number
// Los literales numeros no wrapean directamente a objeto, pero si a través de variable
// El parametro de toString especifica la base del numero que queremos convertir a string
console.log(3.toString(2)); // SyntaxError: Unexpected token ILLEGAL
console.log(numero.toString(2)); // "11"
console.log(new Number(numero).toString(2)); // "11".

// Los arrays son objetos
var array = [1, 2, 3, 4];
console.log(typeof array); // object
// Y por supuesto los objetos son objetos
var my_obj = {propiedad: "valor", propiedad2: "valor2"}
console.log(typeof my_obj); // object

No existen las clases

Los objetos no tienen que ajustarse a una “plantilla” para ser creados, y no tienen métodos y propiedades fijas que comparten todos los objetos de la misma clase… ¡porque no hay clases! Todos los objetos pueden obtener métodos y propiedades al vuelo, sin modificar nada de otros objetos.

// Defino un objeto
var my_obj = {
    propiedad: "valor",
    metodo: function () {
        return "Hola mundo";
    }
}

// Puedo acceder a sus propiedades y metodos
console.log(my_obj.propiedad); // "valor"
console.log(my_obj.metodo()); // "Hola mundo"
// Y agregar nuevas al vuelo
my_obj.propiedad_nueva = "Una propiedad nueva";
console.log(my_obj.propiedad_nueva); // "Una propiedad nueva"

Los objetos se pasan por referencia y los primitivos por valor

Los objetos siempre se pasan por referencia, pero no los primitivos.

// Ejemplo 1
// Copia por valores en tipos primitivos.
var uno = 1;
var dos = uno;
dos = 2;
console.log(uno); // 1
console.log(dos); // 2

// Ejemplo 2
// Copia por referencia en objetos
var uno_obj = {
    valor: 1
};
var dos_obj = uno_obj;
dos_obj.valor = 2;
console.log(uno_obj.valor); // 2
console.log(dos_obj.valor); // 2

// Ejemplo 3
// Paso de parámetros por valor (se pasa un tipo primitivo)
function duplicar(valor, resultado) {
    resultado = valor * 2;
}

var resultado = 0;
duplicar(2, resultado);
console.log(resultado); // 0

// Ejemplo 3
// Paso de parámetros por referencia (se pasa un objeto)
function duplicar(valor, obj) {
    obj.resultado = valor * 2;
}

var obj = {
    resultado: 0
};
duplicar(2, obj);
console.log(obj.resultado); // 4

Semicolon insertion

No importa si terminas las lineas con un punto y coma o no, porque el interprete automáticamente te añade el famoso “;” al final de cada sentencia. Pero hay que tener cuidado con esto, porque puede añadir un “;” en el lugar mas imprevisible posible. Consejo: siempre indica manualmente los finales de linea que correspondan con “;”

function bien() {
    return {
        valor: true
    } // Agregara un ; en este punto. Todo bien.
}

function mal() {
    return // Agregara un ; en este punto, donde no debería
    {
        valor: true
    }
}

console.log(bien().value) // true
// En la funcion mal se ha agregado ; justo despues de return, y por lo tanto devuelve undefined
console.log(mal().value) // TypeError: Cannot read property 'valor' of undefined

Not a Number

Es un valor especial que se suele utilizar como resultado de operaciones matemáticas que no tienen solución. Se representa con NaN, es un estandar del IEEE y hay que tener en cuenta que NaN es distinto de cualquier número, incluido a sí mismo. Es decir, mucho ojo...

console.log(NaN == NaN); // false

No sé exactamente que utilidad vieron a que NaN sea diferente a NaN, pero por definición es así y es algo que hay que tener en cuenta, porque no podemos usar esa comparación para ver si una determinada variable es NaN. Para realizar esta tarea, hay que utilizar la función isNaN();

var nope = NaN;
console.log(nope == NaN); // false
console.log(isNaN(nope)); // true

Equal and exactly equal

Como en varios lenguajes, hay dos tipos de comparación de igualdad (y de desigualidad): ==, !=, === y !==.

La diferencia es que los que tienen dos caracteres (== y !==) solo comparan valores, y los otros dos (=== y !==) comparan valores y también tipo.

Por lo tanto, usar == y != puede llevar a comportamientos extraños.

console.log("0" == 0); // true
console.log("0" === 0); // false

Consejo: Usa siempre los “exactly equal”, es decir, === y !===.

El scope es de función

Cuando definimos una variable (con “var nombre”) esa variable estará definida en toda la función en la que se haya definido, y no solo en el bloque. Es decir, si definimos variables en ifs, whiles o demás bloques, todo lo que declaremos dentro también será visible desde fuera. Es un dato que hay que tener siempre presente, ya que en otros lenguajes como en C, esto provocaría un error de compilación.

function test() {
    var a = 0;
    if (!a) {
        var b = 2;
    }
    console.log(b); // 2
}

test();

Usar o no usar la palabra clave “var”

Usa siempre la palabra clave “var”. Cuando la usas, la variable que has definido se liga al scope de la función actual, con lo que no es accesible desde fuera de esa función. Si no indicas la palabra clave “var”, la variable se ligará al namespace global, con lo que será accesible desde todas tus funciones y desde todas las librerias Javascript que estén cargadas en ese momento.

Nunca es buena idea poner variables en el namespace global. Si todos lo hiciéramos, habría colisiones de variables por todas partes, lo que provocaría que muchas librerías dejaran de funcionar correctamente.

Declarar variables en cadena

Hay que tener cuidado en la forma en la que declaras varias variables en cadena, porque sin quererlo puedes estar creando variables globales en lugar de crearlas en el scope de la función.

function test() {
    var a = b = c = 3;
    // a es local, b y c son globales
    // Esto es porque las asignaciones se leen de derecha a izquierda
    // por lo tanto se podría traducir en:
    // c = 3; (definición global)
    // b = c; (definición global)
    // var a = b; (definición en el scope de la función)

    // Una forma de declarar todas las variables en cadena
    // y en el scope de la función es la siguiente
    var a = 1,
        b = 2,
        c = 3;

    // Si queremos asignarles a todas el mismo valor...
    var a, b, c;
    a = b = c = 3;
}

ECMAScript 5 Use Strict

En ECMAScript 5 se definió un modo especial de interpretación de código que se activa escribiendo una cadena “use strict”. La razón es que hay características de versiones anteriores de ECMAScript que quieren eliminarse en un futuro, pero para no eliminar estas características en una sola versión se implementó una forma de hacer que el interprete se ejecute sin estas características que van a eliminarse. Cuando el interprete de Javascript se encuentra con una cadena de texto que dice “use strict”, solo permite ejecutar un conjunto de ordenes más reducida (pero más que suficiente), con el fin de que las funciones que se han excluido sean eliminadas en futuras versiones del estándar.

Por lo tanto, es recomendable indicar siempre la cadena de texto “use strict”, y que nuestros scripts siempre funcionen con este método, de forma que en futuras versiones de ECMAScript sigan funcionando sin problemas. Hay que escribir la cadena en todas las funciones donde se vaya a activar este modo.

function prueba() {
    "use strict";
    // Resto del código de la función...
}

Variable hoisting

Se podría decir que el parser de Javascript se ejecuta dos veces en lugar de una. En la primera pasada solo tiene en cuenta las declaraciones (“var” y funciones), y en la segunda se ejecuta el código linea a linea. Cuando en la primera pasada encuentra una declaración, declara esa variable como variable ligada a su scope, y después en la segunda pasada es cuando se hacen las asignaciones y demás. Esto permite, por ejemplo, usar una variable en un punto sin la palabra “var”, y unas lineas a continuación declararla como ligada al cope con “var”. Creo que esto se ve más claro con un ejemplo.

// Ejemplo 1
// Pese a que en la linea "a = 3" no estamos usando "var" (y eso implicaría que la variable
// está en el namespace global, y por lo tanto accesible desde cualquier punto, mas
// adelante se declara "var a = 2;". Esto provoca que el interprete enlace la variable al
// scope de la función en lugar de al global (aunque esa linea nunca se ejecute
// y se encuentre más adelante)
function test() {
    a = 3;
    if (a === 0) {
        var a = 2;
    }
}
test();
console.log(a); // ReferenceError: a is not defined

Por esto mismo, es recomendable que todas las declaraciones con “var” se hagan justo arriba de la función, ya que ahorra muchos de estos problemas y deja muy claro cuales serán las variables locales de esa función. A eso se le conoce como “Single var pattern”. También es una buena práctica asignarles a todas un valor por defecto.

function test() {
    // Single var pattern
    var a = 2,
        b = "hola",
        c = 3;

    // El resto del código
    // Sin ninguna declaración de variables más
}

JSLint: Javascript Code Quality Tool

Hay una herramienta online llamada JSLint que parsea nuestro código y nos permite corregir algunos “bad smells” (patrones o acciones que sintácticamente son correctos, pero pueden provocar fallos no deseados ahora o en el futuro). También tiene muchas normas de estilo, con la ventaja de que permite activar y desactivar las opciones que queramos.
Es muy útil, y os recomiendo que le echéis un ojo y se lo paséis a vuestro código cuando podáis.

Referencias

http://www.ecmascript.org/
http://es.wikipedia.org/wiki/JavaScript
http://es.wikipedia.org/wiki/ECMAScript
http://www.amazon.es/dp/0596517742
http://www.amazon.es/JavaScript-Patterns-Stoyan-Stefanov/dp/0596806752
http://www.adequatelygood.com/JavaScript-Scoping-and-Hoisting.html
http://www.jslint.com/

Quiero hacer dos partes más sobre Javascript, una sobre Objetos y otra sobre Funciones, y ambas con pinceladas de algunos patrones útiles a la hora de crear objetos o librerías. Si necesitáis cualquier cosa, estoy disponible a través de la caja de comentarios.