Seleccionar página

Cuando trabajamos con datos en Javascript nos encontramos con algunos casos algo complicados de gestionar entre los que están undefined, null y NaN. Con estos datos debemos tener en cuenta su especial comportamiento y naturaleza. Vamos a dar un repaso a estos tipos de datos con la excusa del desarrollo de una función universal de comparación de cualquier tipo de datos en Javascript que venimos desarrollando en esta serie de artículos.

undefined

Cuando una variable ha quedado sin definir tiene un valor especial denominado undefined, que se podría traducir como “sin definir. Este valor puede resultar un poco desconcertante, ya que además de un tipo de datos, existe una variable con ese nombre y también es un valor como tal.

En primer lugar undefined es es un tipo de dato y por lo tanto se puede consultar con typeof.

var variable;
if (typeof variable === "undefined") {
    console.log('ok');
}

Aunque estrictamente no es parte de la especificación del lenguaje EcmaScript, en todas las implementaciones existe una variable global que contiene este valor y que se denomina también undefined, por lo que podemos hacer comparaciones de este tipo:

var variable;
if (variable === undefined) {
    console.log('ok');
}

Un error que se comete a menudo, y que no tienen ningún sentido, es comparar la variable undefined con el resultado de typeof. Esta comparación siempre devolverá false, ya que typeof siempre devolverá una cadena de texto con el tipo.

var variable;
if (typeof variable === undefined) {
    console.log('ok');  // No se ejecuta
}

Otro problema al comparar con la variable undefined, es que si la variable que estamos analizando no ha sido definida con var, let o const la comparación producirá un error. Por ello, siempre es recomendable utilizar typeof para comprobar si se ha definido una determinada variable.

También tenemos que tener en cuenta que undefined no es una palabra reservada de Javascript y por lo tanto podemos definir una variable con ese nombre. Si lo intentamos en el contexto global nos encontraremos que esa variable ha sido definida como {writable: false, configurable: false} y no podemos cambiarla, pero sí podemos hacerlo dentro del alcance de una función, aunque sin duda es una muy mala idea:

function test() {
    var variable;
    var undefined = 10;
    if (variable === undefined) {
        console.log('ok');  // No se ejecuta
    }
}

Por último, debemos recordar que el operador void devuelve siempre un valor undefined, sea cual sea la expresión que le situemos a su derecha y puede ser de utilidad en alguna ocasión. Lo cierto es que instrucciones como void(0) son ahora poco habituales, pero son perfectamente válidas. En el caso anterior podríamos haber comparado con variable === void(0) y hubiéramos obtenido un resultado correcto.

null

El caso del valor nulo es también bastante singular. En este caso null es un literal definido en la especificación del lenguaje, no una variable global como en el caso undefined, por lo tanto, es una palabra reservada y no podremos utilizarla como nombre de variable.

Podemos directamente utilizar el literal null para comprobar este valor es el que contiene una determinada variable o es el retorno de una función.

var variable = null;
if (variable === null) {
    console.log('ok');
}

A diferencia de undefined, null no dispone de un tipo específico y en el caso de preguntar con typeof sobre un elemento con valor null nos dirá que es un objeto, lo cual puede desconcertarnos, ya que en la práctica no podemos tratarlo como al resto de objetos.

var variable = null;
if (typeof variable === "object") {
    console.log('ok');
}

Que null es un objeto es algo peculiar y debemos tener en considerar que una variable que dice ser un objeto realmente puede contener null y producir un error en nuestro código si lo lo validamos.

NaN

Cuando trabajamos con valores numéricos tenemos que tener en cuenta la existencia de NaN (Not a Number). Aunque realmente sí es un valor de tipo numérico, no podemos operar con él como con el resto de valores numéricos, ya que cualquier operación que se realice con NaN siempre devolverá NaN.

Normalmente se obtiene NaN como resultado de las operaciones con funciones y métodos de Math donde no ha sido posible convertir un valor a numérico o se ha producido algún error. Ejemplos sencillos en los que se obtiene este tipo de valor son:

Math.sqrt(-1);
parseInt('no');

El problema a la hora de comprobar si un valor es NaN es que si usamos typeof nos dirá que es un valor numérico y podemos considerar que tenemos entre manos un número válido.

El segundo problema es que para comprobar si es un número no válido no podemos comparar con la variable global con nombre NaN, ya que siempre obtendremos false (NaN === NaN siempre devuelve false). Como consecuencia no podemos comparar NaN de una forma natural.

var variable = parseInt('xxx');
if (typeof variable === 'number') {
    console.log('Is a number');   // Se ejecuta
}
if (variable === NaN) {
    console.log('Is NaN');      // No se ejecuta
}

Para resolver este problema existe la función isNaN(), y desde ES6 también la encontramos como Math.isNaN(). Esta función devolverá verdadero si el valor pasado como parámetro es NaN o no es un valor numérico. Esto complica bastante el trabajo con este tipo de valor.

var variable = parseInt('xxx');
if (isNaN(variable)) {
    console.log('Is NaN');      // Se ejecuta
}

En este caso también tenemos que tener en cuenta que NaN no es una palabra reservada de Javascript y por lo tanto podemos definir una variable con ese nombre. Igual que en el caso de undefined, si intentamos cambiar esta variable en el contexto global nos encontraremos que ha sido definida como {writable: false, configurable: false} y no podemos cambiarla, pero sí podemos hacerlo dentro del alcance de una función, aunque de nuevo es una muy mala idea:

function test() {
    var variable = 10;
    var NaN = 10;
    if (variable === NaN) {
        console.log('ok');  // Se ejecuta
    }
}

Comparar null o undefined

Para incorporar la comparación de null o undefined a nuestra función de comparación universal de cualquier tipo de valor en Javascript -que venimos desarrollando en esta serie de artículos- podemos aprovechar que ya se han realizado comparaciones estrictas y no estrictas entre los dos valores y por lo tanto, podemos tener en cuenta que cualquier valor comparado con null o undefined será siempre false.

Para gestionarlo incluiremos algunas lineas a nuestra función equal():

function equal(a, b, options) {
    var aType = typeof a, bType = typeof b; // Get value types
    options = options || {};                // Optional parameter
    if (a === b) {                          // Strict comparison
        return equal.VALUE_AND_TYPE;        // Equal value and type
    }
    if (options.nonStrict && a == b) {      // Non strict comparison (optional)
        return equal.VALUE;                 // Equal value (different type)
    }
    if (aType === 'undefined' ||            // undefined and null are always different than other values
        bType === 'undefined' ||
        a === null ||
        b === null)
    {
        return equal.NOT_EQUAL;
    }
    return equal.NOT_EQUAL;                 // Not equal
}
equal.NOT_EQUAL           = 0;
equal.VALUE               = 1;
equal.VALUE_AND_TYPE      = 2;

Versión de esequal.js en GitHub y sus pruebas.

Lo cierto es que en este estado no parece que hayamos avanzado mucho, ya que ya estábamos retornando equal.NOT_EQUAL, pero lo cierto es que nos facilita el camino para avanzar con seguridad en los próximos pasos.

Comparar NaN

Para incorporar la comparación con NaN en nuestra función debemos comprobar si es de tipo numérico y hacer uso de isNaN() para los dos valores que estamos comparando, entonces debemos considerar que si en ambos casos devuelve true, son equivalentes. Para ello incorporamos algunas líneas adicionales a nuestra función equal().

function equal(a, b, options) {
    var aType = typeof a, bType = typeof b; // Get value types
    options = options || {};                // Optional parameter
    if (a === b) {                          // Strict comparison
        return equal.VALUE_AND_TYPE;        // Equal value and type
    }
    if (options.nonStrict && a == b) {      // Non strict comparison (optional)
        return equal.VALUE;                 // Equal value (different type)
    }
    if (aType === 'undefined' ||            // undefined and null are always different than other values
        bType === 'undefined' ||
        a === null ||
        b === null)
    {
        return equal.NOT_EQUAL;
    }
    if (aType === 'number' &&               // Special case: Not is a Number (NaN !== NaN)
        bType === 'number' &&
        isNaN(a) &&
        isNaN(b)) {
        return equal.VALUE_AND_TYPE;
    }
    return equal.NOT_EQUAL;                 // Not equal
}
equal.NOT_EQUAL           = 0;
equal.VALUE               = 1;
equal.VALUE_AND_TYPE      = 2;

Versión de esequal.js en GitHub y su juego de pruebas.

Conclusiones

Javascript está lleno de sorpresas. Nos encontramos datos como NaN que tienen un comportamiento absolutamente singular y que tenemos que tener muy en cuenta para que nuestros programas se comporte adecuadamente. La existencia undefined puede ser algo confusa, ya que es un tipo y también un dato. El hecho de que null es identificado como de tipo objeto, hacen que tengamos que tener en cuenta este hecho antes de intentar tratarlo como un objeto. Estos casos se deben tener siempre presentes ya nos puede producir problemas o errores en nuestro código.

Nuestra función universal para comparar valores de cualquier tipo en Javascript va tomando forma, todavía es muy escasa su funcionalidad, pero nos está sirviendo como excusa para a dar un repaso a muchas características importantes e interesantes del lenguaje. En los próximos capítulos avanzaremos de forma significativa, pero, si no puedes esperar, descarga la versión final de esqueal.js con npm.

Novedades

Explorando ArrayBuffer, DataView y matrices con tipo

Hasta hace relativamente poco en Javascript era complicado gestionar datos binarios. ArrayBuffer, DataView y las matrices con tipo (Typed Array) ponen a nuestra disposición un conjunto bastante completo de herramientas para manejar tipos binarios sin problemas. Vamos a ver cómo funcionan…

Objetos Map y Set

Los objetos Map y Set nos pueden ser de gran ayuda para gestionar conjuntos de datos, pudiendo simplificar nuestros programas en muchas circunstancias. Es interesante que sepamos cómo se utilizan y que pequeños secretos esconden. Vamos a revisarlos…

Referencia circular en objetos

Todos sabemos que los objetos pueden contener otros objetos, pero de lo que quizás no somos conscientes es que con mucha facilidad podemos crear una referencia circular, es decir, que si recorremos las propiedades del objeto y vamos profundizando, llegamos de nuevo al objeto inicial. Debemos tener en en cuenta esta circunstancia a la hora de realizar algunas operaciones o tendremos problema. Veamos cómo…

Características de las propiedades de los objetos

Existen varios tipos de propiedades que se comportan de forma diferente. Tenemos que tener en cuenta es la diferencia entre propiedades enumerables y no enumerables, propias y heredadas, de sólo lectura o no configurables, sin olvidar alguna que otra convención para definir propiedades como privadas. Veamos cómo trabajar con los distintos tipos de propiedades de un objeto.

breves

Descrubir algunas características de console

En el día a día nos encontramos muy a menudo utilizando console. Es una navaja multiusos que nos facilita la vida a la hora de depurar nuestro código. La mayoría de nosotros ha utilizado console.log(), pero tiene otras muchas funcionalidades.

Matrices dispersas o sparse arrays en JS

Una característica que puede producir algunos problemas, si no lo tenemos en cuenta, es la posibilidad de tener matrices con huecos, es decir, con algunos de sus elementos sin definir. Es lo que se suele denominar una matriz dispersa o sparse array. Veamos cómo trabajar con esta características de las matrices.

Algunos operadores de bits usados con asiduidad

Cada día más a menudo podemos encontrar operadores binarios utilizados como formas abreviadas de algunas operaciones que de otra forma sería algo menos compactas y, quizás, más comprensibles. Veamos algunos casos en detalle.

Cómo diferenciar arrow function de function

En un reciente artículo Javier Vélez Reyes hace patente las principales diferencias entre las funciones tradicionales y las funciones flecha, ya que ambos modelos no son equivalentes e intercambiables. Veamos cómo es posible saber si una función ha sido construida por medio de la instrucción function o como una arrow function.