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

HTTP2 para programadores. Enviar mensajes del servidor al cliente con Server Sent Event (sin WebSockets)

HTTP2 para programadores. Enviar mensajes del servidor al cliente con Server Sent Event (sin WebSockets)

En esta charla, organizada por MadridJS, Pablo Almunia nos muestra cómo la mayoría de nosotros cuando oímos hablar por primera vez de HTTP2 nos ilusionamos con las posibilidades que presumiblemente se abrían para el desarrollo de soluciones web avanzadas y cómo muchos nos sentimos defraudados con lo que realmente se podía implementar.

En esta charla podemos ver cómo funciona el HTTP2, que debemos tener en cuenta en el servidor para hace uso de este protocolo y, sobre todo, cómo podemos enviar información desde el servidor al cliente de forma efectiva y fácil. Veremos con detenimiento cómo por medio de los Server-Sent Events (SSE) podemos recibir en el cliente datos enviados desde el servidor sin utilizar websocket, simplificando enormemente la construcción de aplicaciones con comunicación bidireccional.

Observables en Javascript con Proxies

Observables en Javascript con Proxies

En esta charla, organizada por MadridJS, Pablo Almunia nos habla de la observación reactiva de objetos en Javascript por medio de Proxy. Se describe paso a paso cómo funcionan los Proxies y en que casos pueden ser nuestro mejor aliado. Veremos que no hay que tenerles miedo, son bastante sencillos de utilizar, y nos ofrecen una gran abanico de posibilidades.

Aplicaciones JAMStack, SEO friendly y escalables con NextJS

Aplicaciones JAMStack, SEO friendly y escalables con NextJS

En esta charla de Madrid JS, Rafael Ventura nos describe las funcionalidades clave de NextJS, nos muestra en vivo cómo desarrollar una completa aplicación JAMStack con Server Side Rendering (SSR) y Static Site Generation (SSG) y termina mostrando como publicar esta aplicación en Vercel.

Stencil JS: mejora el Time To Market de tu producto, por Rubén Aguilera

Stencil JS: mejora el Time To Market de tu producto, por Rubén Aguilera

En esta charla Rubén Aguilera nos cuenta los problemas que tienen muchas empresas a la hora de sacar productos accesibles, vistosos y usables en el Time To Market que requiere Negocio y cómo podemos minimizar este tiempo gracias al DevUI con StencilJS para adecuar una aplicación de Angular a las exigencias del mercado en tiempo record.

breves

Javascript: 25 aniversario

25 años con Javascript han dado para mucho. Pero todavía queda mucho para este lenguaje de programación.

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.

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.