RSS Feed

Detrás de escena: wrapper objects

6

marzo 8, 2013 by - @MatiasArriola

Muchas cosas de las que voy a compartir ahora, las aprendí hace relativamente poco y me permitieron entender algunos de los WTF que se ven en javascript. Creo que el típico 'string' vs. new String('string') es uno de los conceptos que uno dá por sentado y nunca se cuestiona lo suficiente.

Cuando leí este artículo que habla sobre la performance en objectos String, aparte de pensar “que bueno debe ser tener tiempo para hacer experimentos como esos”, me topé con la teoría detrás de los ‘wrapper objects’. De ahí, llegué a este link que me parece absolutamente recomendable (al igual que el resto del blog). Casi todo lo que escriba de acá en adelante, tal vez sea parafrasear o hablar un poco más de lo mismo que en esos enlaces.

En JavaScript, los strings, los numeros, los booleanos, no son objetos; son tipos primitivos. Por consiguiente, no tienen propiedades. Es decir, ‘un string’ no tiene ni indexOf, toUpperCase, ni length… Nada.

¿Entonces?

Después de leer eso, vale la pena abrir la consola de javascript, ¿ Por qué puedo hacer 'fernet'.concat('js') ???

La respuesta es sencilla: por detrás, lo que ocurre es que el string ‘fernet’ es automáticamente convertido a un objeto String, es decir a new String('fernet'), y ese objeto es el que tiene todas las propiedades y métodos que conocemos; por eso es posible aplicarle el concat.
El método concat devuelve un string primitivo, aunque la misma conversión automática ocurriría si quisieramos utilizar alguna de sus propiedades.

typeof 'fernet' // "string"
typeof 'fernet'.concat('js') // "string"
'fernet'.concat('js').toUpperCase() // "FERNETJS"

Vuelvo a insistir con lo mismo para aquellos que no acostumbren a hacerlo: experimentar con la consola js de las herramientas de desarrollo de su navegador favorito es una de las mejores maneras de aprender, descubrir nuevos conceptos, y reconfirmar las cosas que ya sabemos.

var a = 'ejemplo',
    b = new String('ejemplo');

typeof a //"string"
typeof b //"object"
a == b //true
a === b //false
a + b //"ejemploejemplo"

// puedo consultar propiedades para ambos
a.length //7 (gracias a la conversión implícita a su wrapper obj) 
b.length //7

Por si sigue sonando extraño, esto sería lo que hace el intérprete de javascript “detrás de escena” cuando hacemos ‘FERNETJS’.toLowerCase():

var stringOriginal = 'FERNETJS';
var str = new String(stringOriginal); //str sería el wrapper object 
str.toLowerCase(); // 'fernetjs'

El intérprete no sólo usa este “wrapper object” detrás de escena cuando queremos obtener una propiedad, sino que también cuando la queremos setear; pero esto puede ser contraproducente:

var str = 'mmm';
str.unaPropiedadInventada = 100;
str.unaPropiedadInventada // undefined
var str2 = new String('jeje');
str2.unaPropiedadInventada = 100;
str2.unaPropiedadInventada //100

Sobre la conversión [tipos primitivos]<->[wrappers]

Nosotros podemos trabajar con tipos primitivos o con objetos, y podemos explícitamente hacer conversiones en ambas direcciones.

// con new Number envolvemos al primitivo 1 en un objeto number
var n = new Number(1);
typeof n //"object"
// llamando a Number (sin new) convierte el obj a su primitivo
typeof Number(n) //"number"

De todas maneras, no es recomendable y rara vez o nunca, vamos a escribir en nuestro código var x = new Number(17). La principal razón, es que no nos va a ser de gran utilidad, ya que sea cual sea el valor del objeto, siempre va a ser "verdadero" en el resultado de una evaluación:

var tengoPlata = new Boolean(false);
if (tengoPlata)
    console.log('comprar unos fernecitos');
else 
    console.log('esperar');

En el caso anterior, por ejemplo, quería decir que no tenía plata. De todas maneras, se ejecuta "comprar unos fernecitos", dejándome en bancarrota. Esto ocurre porque tengoPlata es un objeto, en contraste con el false que hubiera sido más natural usar.

Por otra parte, desde objeto hacia tipo primitivo muchas veces se hacen conversiones automáticas, (del mismo modo que 1 podría ser convertido a '1'), más que nada a la hora de operar.
Por ejemplo:

var x = new Number(199),
    y = 1;
x + y // 200

Estas conversiones se basan en lo que devuelve el valueOf() del objeto. Es decir, siempre que se trate de convertir un objeto a un primitivo, el resultado de la conversión va a ser lo que devuelve el valueOf. Este método está en todos los objetos, y si no es sobreescrito, va a devolver el mismo objeto (docs de valueOf).
Lo más loco de este artículo es lo que sigue. Sabiendo esto, uno podría emular un objeto, que al momento de operar sería convertido a su correspondiente valor primitivo.

var a = {
  valueOf: function(){ return 199; }
};
console.log(a + 1); //200

var b = { 
    valueOf: function(){ return navigator.plugins.length; } 
};
console.log(b - 1); //???? jeje

¿De qué me sirve saber esto?¿Ya puedo hacer juegos multiplayer en 4-D con js?

La verdad que al fin y al cabo todo esto no tiene mucho sentido práctico, uno podría vivir sin saberlo, pero ayuda a conocer un poco más el lenguaje y entender por qué las cosas funcionan de la manera que lo hacen.
Sí es importante tener presente los problemas que puede traer manualmente instanciar estos objetos String, Number, etc..

NOTAS: * A lo largo de todo el artículo se utilizó el termino 'wrapper object'. Esto se debe a que no encontré una traducción acorde, 'objeto envolvedor', 'objeto envoltorio' simplemente no sonaban correctos. Sugerencias?
* Cuando se habla de String, Number, Boolean en los ejemplos, la mayoría de las veces se podrían haber aplicado indistintamente.


  • http://twitter.com/AlejoNext Alejandro Ñext

    Te contradecirse en un cosa, como tal no tiene un uso practico.. pero si sale el típico erro “IndexOf is not a Pro…”. Que a todos nos ha salido, lo mejor es utlizar un “new String( str )”, así nos evitamos el error!!

    • matiasarriola

      Hola Alejandro, el único uso práctico que se me ocurre es agregarle propiedades dinámicamente al objeto.
      A mí nunca me saltó el error ese, no entendí bien a que te referís

      • http://twitter.com/AlejoNext Alejandro Ñext

        Pues… a mi me ha salido mucho el error “Indexof string doesn’t support this property or method”. Es un problema común, y ahy esta la respuesta! y la solución a ese problema!

        • Fede

          Alejandro, estás confundido, el error que decís se debe a que escribís incorrectamente un método, en este caso la forma correcta es indexOf()

  • http://twitter.com/emiliocobos95 Emilio Cobos Álvarez

    Y esta es la razón por la que no se debe (por lo general) usar constructores como Array y Object :)

    Buena explicación.

  • Pingback: Allan