Coerción de datos en Javascript

06 Abr 2011

La coerción de datos es uno de los aspectos más intrigantes de Javascript. Al corresponderse con un lenguaje de tipado blando, no es necesario especificar el tipo de datos que contendrán nuestras variables. Esto hace posible que en tiempo de ejecución, podamos modificar dicho tipo para operar con ellos a voluntad.

Por lo general, es el intérprete de Javascript el que realiza la conversión de tipos por nosotros de forma interna buscando adaptarse a nuestras operaciones.

Por ejemplo, echemos un vistazo al siguiente código:

var a = "1",
    b = 5;
 
console.log( typeof a, typeof b); // string number
console.log( a + b ); // 15

El resultado puede no ser el esperado: intérnamente, Javascript realiza la conversión del número a cadena y concatena los operandos.

Pero esto es así porque el operador + es a la vez concatenador y suma, prevaleciendo la primera de sus funciones cuando la situación lo permite.

Cuando utilizamos el operador de resta, la cosa cambia:

console.log( "2" - 2 ); // 0

En este caso, la coerción se realiza desde la cadena al número para permitir la operación. Con la multiplicación y la división, ocurre igual:

console.log( '10' * 3 ); // 30
console.log( '10' / 2 ); // 5

Otro ejemplo interesante es cuando involucramos valores lógicos:

console.log( true + true ) // 2

El valor devuelto es el resultado de la suma cuyos operandos corresponden con la conversión de los operadores booleanos a su entidad numérica ( true === 1 ). De nuevo, el intérprete realiza la coerción por nosotros en segundo plano.

De ahí se deduce que,

console.log( '1' + true ); // 1true
console.log( '10' - true ); // 9

Más coerción!

Los resultados hay que reconocer que pueden resultar, cuanto menos, desconcertantes. La siguiente tabla con diversas curiosidades derivadas de la coerción ha sido elaborada por Angus Crall:

true == 1; //true! (converts true to bit value 1)
true - false === 1 //true!!!
"2" + 2; //22!
"2" - 2; //0
"2" - - 2; //4
NaN == NaN; //false - this one sort of makes sense since NaN is any non-Number cast to a number
NaN  NaN; //false - whatever!
"" == 0 //true
undefined == null //false!
"0" == 0 //true!
"false" == false //false!!!'
'\t\r\n ' == 0 //true!!! - essentially an empty string which coerces to 0

NOTA: Algunos de las comprobaciones anteriores no pueden realizarse desde la consola de Firebug ya que arrojan errores de sintaxis. Realmente, se trata de un problema de Firebug para ejecutar algunas instrucciones de este tipo.

Con los condicionales, la situación es similar:

if (0) {....} //false
if(-1) {....} //true
if("") {....} //false
if("null") {....} //true

Forzando la coerción

Como otros lenguajes no tipado, Javascript permite realizar una conversión explícita de los tipos de datos mediante funciones nativas como parseInt, parseFloat, etc…

Es posible realizar la conversión entre casi todos los tipos existentes. La siguiente tabla (vista aquí) nos muestra cómo conseguir cada una de estas conversiones:

From To Use this
string boolean !!x
string number x – 0
string object new String(x)
boolean string ” + x
boolean number x – 0
boolean object new Boolean(x)
number string ” + x
number boolean !!x
number object new Number(x)
object string x.toString()
object boolean !!x -or- !!x.toString()
object number (!!x) – 0 -or- x.toString() – 0

Conclusión

La coerción de datos puede ser algo realmente poco intuitivo y capaz de generar los errores más difíciles de rastrear. Conocer algunas de las conversiones que realiza el intérprete Javascript en segundo plano puede ayudarnos a evitar estos comportamiento no deseados en el tratamiento de nuestros datos.

Además de la conversión automática, disponemos de operadores lógicos y unarios que pueden ayudarnos a pasar de un tipo de dato a otro según necesitemos, algo que en muchas ocasiones, no resulta tan sencillo como debiera.

Más:

{11} Comentarios.

  1. Leandro

    Hola,
    Para el primer ejemplo, en que se suma (string + number), también es posible convertir el string a number anteponiendo + al string: console.log( +a + b ); // 6

    Saludos

    • Carlos Benítez

      Correcto Leandro;
      en la tabla encontramos alguna formas más de convertir cadenas a números.
      Sin embargo, los test de rendimiento demuestran que el operador + es incluso más rápido que un parseInt a la hora de realizar la conversión, por lo que suele ser mi opción favorita.

      Saludos!

  2. Ñuño Martínez

    Cuanto más aprendo de JavaScript más me gusta Pascal. En serio. Incluso estoy pensando en que se mi lenguaje más odiado, superando a PHP y C++ (y he dicho C++, no C).

    Por cierto, ¿la siguiente línea es legal, o es una errata?

    “false” = false //false!!!

    ¿Cómo narices puede asignarse una constante de cadena?

    • Carlos Benítez

      Si; efectivamente era una errata. Gracias!

      Javascript es un lenguaje que puede despertar reacciones tanto apasionadas como de odio. Solo hay que tomarse el tiempo para entenderlo y ver cómo sacar partido de sus particularidades.
      Precisamente, la coerción de tipos, es una herramienta muy potente cuando la utilizamos de forma consciente: ofrece un alto grado de flexibilidad que después se echa de menos en lenguajes de tipado fuerte.

      Saludos!

  3. Ñuño Martínez

    Por alusiones (y sin rencor, ¿eh? ;):

    Creo que el problema que tengo con JS es porque lo aprendí de textos que lo manejaban como si fuera un lenguaje “procedimental” y “estructurado”, comparándolo excesivamente con C y Java, y claro, parece ser que no es así. Ahora, después de tantos años (empece con JS allá por el noventaytantos) a ver cómo reaprendo y me quito de encima todo ese bagaje prejuicioso…

    Y de nada. 🙂

  4. Andrés

    ¡Hola! Lo primero, agradecerte este trabajazo que haces porque es muy útil y beneficioso para todo aquel que quiere aprender y/o mejorar en programación. Dicho esto, sólo comentar que hay una pequeña errata en el ejemplo:

    console.log( ’10’ – true ); // 99

    En realidad daría 9 (o el ’10’ debería ser un ‘100’). Un detalle tonto, pero ante un trabajo tan bueno como el que haces, si veo una errata no puedo ignorarla ¡Deben ser erradicadas! Jajaja (vaya, qué malévolo ha quedado eso…).

    ¡Un saludo!

    • Carlos Benítez

      Tienes toda la razón Andrés;
      ya lo he corregido.

      Gracias por el reporte 😉
      Un saludo!

  5. dhamaso

    Gracias por la explicación pero lo que no acabo de entender es esto :
    a) !!x
    b) “2” – – 2; //4

    ¿Que pasa en esos casos?
    ¿Como es que funcionan?

    • Carlos Benítez

      Hola;
      si, veamos; explicar la coerción de datos en determinados ejemplos nunca es fácil porque implica conocer cómo funciona el intérprete de Javascript, algo que no siempre resulta obvio o intuitivo:

      Doble Negación:
      En Javascript, como en otros lenguajes de programación, disponemos del operador lógico NOT identificado como un signo de admiración (!). Este operador devuelve FALSE si el operando sobre el que actúa, en este caso una variable, puede ser convertido en TRUE; en caso contrario, devuelve TRUE.

      Lo anterior quiere decir que si la variable posee un ‘Thruly Value’, o lo que es lo mismo, algún valor que no sea ‘Falsy’ (ver el artículo correspondiente a los falsy values en Javascript), el operador devuelve lo contrario, es decir, FALSE. Por lo tanto:

      var x = 'HelloWorld'; // Thruly value
      console.log( !x ); // false
      
      var x = ''; // Falsy value
      console.log( !x ); // true
      

      El ejemplo anterior parece claro. La doble negación supone pues volver a preguntar lo mismo sobre el resultado de la negación anterior. Se ve más claro con paréntesis:

      var x = 'HelloWorld'; // Thruly value
      console.log( !( !x ) ); // true
      

      Lo que ha pasado aquí, es similar a:

      console.log( !false );
      

      Ya que !x era false.

      Por tanto, el intérprete está evaluando con la nueva negación si el objeto ‘false’ (que resultó de la primera) puede convertirse a TRUE o no.
      Para entender el resultado final, hay que volver al principio, a la primera negación. Aunque Javascript devuelva ‘false’, lo importante es saber qué tipo de objeto ha devuelto:

      var x = "Hello World";
      console.log( !x ); // false
      console.log( typeof(!x) ); // boolean
      

      Bien, sabemos que no es una cadena, sino un booleano. Pues bien, al volver a preguntar por el contrario de un booleano es fácil ya que solo acepta dos valores (true y false):

      console.log( !true ); // false
      console.log( !false ); // true
      

      Y ahora ya si entendemos el resultado original: al evaluar de nuevo una cadena que era falsa, nos da verdadero y viceversa. De ahí que funcione el primer ejemplo que has indicado.

      Veamos el segundo:

      console.log( "2" - - 2 ); // 4
      

      Parece muy poco intuitivo el resultado que nos devuelve el intérprete pero, de nuevo, si utilizamos paréntesis quizás nos aclare el orden de las operaciones que se realiza en segundo plano:

      console.log( "2" - ( - 2 ) ); // 4
      

      La evaluación de operaciones se realiza de derecha a izquierda, por eso, el ( – 2 ) es lo primero en evaluarse. En este caso, el negativo de un entero es el mismo número acompañado del signo menos, es decir:

      console.log( "2" - ( -2 ) ); // 4
      

      He quitado el espacio entre el signo menos y el 2 para que se note que estamos ahora ante un entero negativo. El resto es fácil: Javascript quiere restar a la cadena “2”, un número negativo (-2) y, por coerción de tipos, siempre que una cadena opera con un número, ésta trata de evaluarse como tal. Es decir, que Javascript va a tratar de convertir el “2” en 2:

      console.log( 2 - ( -2 ) ); // 4
      

      De ahí el resultado original.

      Espero que la explicación haya quedado clara ya que, como te indiqué más arriba, resulta complicado desmenuzar los pasos que sigue el intérprete cuando se trata de coerción.

      Saludos!

  6. dhamaso

    Genial ahora si ya entendi, mas claro ni el agua!!! gracias

  7. Sergio Morchón

    Hola Carlos.
    Muchas gracias por este artículo; ¡me ha sido de muchísima utilidad!

    Se me ocurre que podrías mencionar también el tipo Date con constructor numérico, como new Date(NaN)//InvalidDate
    Su comportamiento en cuanto a coerción es muy similar al numérico:
    new Date(NaN) === new Date(NaN)//false

    Y ¿tendría también cabido lugar Constructores vs Wrappers?
    Me explico:
    new String(“test”) === String(“test”)//false
    String(“test”) === String(“test”)//true
    typeof String(“test”);//”string”
    typeof new String(“test”);//”object”
    Number(8) === new Number(8);//false

    Comento esto último porque es muy frecuente el uso sin conocimiento de las funciones String (el caso que más veces he visto) como constructor (new String(“test”)), lo que devuelve un OBJETO con prototipo String, que no es lo mismo que llamarlo como wrapper (como una función normal: String(“test”)), que devuelve un tipo PRIMITIVO.

    Esto provoca serios dolores de cabeza, ya muy de la mano con este tema de coerción de datos.

    Nuevamente muchas gracias por tu aporte.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *