Nuevos objetos y propiedades ECMAScript 5

07 Mar 2011

Cuando en diciembre de 2009 finalmente se completó y publicó la quinta edición del ECMA-262, más conocida como ECMAScript 5, se llevó a cabo la actualización más importante del núcleo de Javascript de los últimos diez años.

Teniendo en cuenta que este lenguaje está presente en todos los navegadores web modernos de uno u otro modo, el tema de la compatibilidad con versiones anteriores fue muy tenido en cuenta. ES5 supone un esfuerzo considerable en la estandarización (o codificación) de algunos comportamientos o estructuras que la comunidad de desarrolladores ha ido adoptando como estándares de facto durante estos últimos años. Una muestra de ello pueden ser algunas de las funcionalidades que ofrencen librerías de terceros como jQuery o MooTools que, aún sin contar con soporte nativo, son ampliamente utilizadas: pienso por ejemplo en la función trim de la que hemos hablado en alguna ocasión en este blog, o en un método bind para asociar eventos directamente a los elementos de una página web.

De forma adicional, la introducción del modo estricto en la nueva especificación abre el camino para que los desarrolladores puedan ir adoptando las nuevas directrices que se continuarán en las próximas revisiones.

Cambios en el Object Model

Pero de entre tanto cambio, una de las actualizaciones más interesantes es, sin lugar a dudas, la revisión de modelo de objetos tradicional.

Los objetos en Javascript, desde la tercera revisión de la especificación, son colecciones de propiedades, con ciertos atributos propios que permitían determinados grados de control sobre su estructura. Sin embargo, lejos de otros lenguajes orientados a objetos más puros (o tradicionales), se echaban en falta una serie de métodos más precisos con los que operar.

Nuevos atributos de las propiedades de un objeto

En esta vía, con la nueva especificación ES5, se han añadido una serie de atributos a cada una de las propiedades de un objeto que permiten un grado de control mucho mayor. Los nuevos atributos son:

  • value: el valor del atributo.
  • enumerable: controla si una propiedad es enumerada dentro de un bucle for-in. Valor por defecto: true.
  • configurable: controla si una propiedad puede ser borrada en tiempo de ejecución. Valor por defecto: true.
  • writable: controla si una propiedad puede ser sobreescrita. Valor por defecto: true.
  • get: función que fija un valor de retorno cuando es accedida. Valor por defecto undefined.
  • set: función que es llamada con un valor determinado valor cuando se le asigna una propiedad. Valor por defecto undefined.

Asignando Propiedades a un objeto

Visto los atributos, podemos jugar con las propiedades de un objeto para ver cuáles son los nuevas funcionalidades que ofrecen.

En ECMAScript 5, para asignar una propiedad a un objeto, podemos hacerlo de dos formas diferentes.

Una es la manera tradicional, en la que simplemente asignamos un valor de forma directa:

var circle = {};
circle.radius = 4;

La otra forma es mediante el uso del método Object.defineProperty:

var circle = {};
Object.defineProperty( circle, "radius", {
  value : 4,
  writable : false,
  configurable : false
})

La sintaxis del nuevo método, como puede comprobarse en el ejemplo, es la siguiete:

Object.defineProperty(obj, prop, descriptor)

Donde:
obj: es el objeto en el que definimos la propiedad.
prop: es el nombre de la propiedad que estamos definiendo.
descriptor: es la lista con los atributos antes referenciada y sus correspondientes valores.

Nota Importante

Si copiamos el código anterior en la consola Firebug para realizar las comprobaciones, en la versión más reciente mientras se escribe este artículo (1.7X.0a11), un console.dir() da como resultado un objeto vacío:

console.dir( circle ); // No hay objetos hijos

Sin embargo, llamando directamente a la propiedad, si que obtenemos el valor asignado:

console.log( circle.radius ); // 4

Además de asignar una nueva propiedad a un objeto, este método permite actualizar el descriptor siempre que el atributo configurable de dicha propiedad esté definido como true:

var circle = { radius : 4 };
var descriptor = Object.getOwnPropertyDescriptor( circle, "radius" );
 
descriptor.configurable = false;
 
Object.defineProperty( circle, "radius", descriptor );
delete circle.radius;
 
console.dir( circle ); // radius 4

En el código anterior hemos nombrado un descriptor único para la propiedad radius del objeto circle mediante el método Object.getOwnPropertyDescriptor.

La sintaxis de este método es la siguiente:

Object.getOwnPropertyDescriptor(obj, prop);

Donde:

obj, es el objeto en el que buscamos la propiedad.
prop, es el nombre de la propiedad de la que vamos a obtener la descripción.

Luego, hemos modificado uno de sus atributos directamente para, finalmente, pasarlo todo el descriptor como tercer argumento del método defineProperty.

Al especificar que la propiedad no es configurable, ésta no puede eliminarse, por lo que cuando realizamos la consulta con el console, obtenemos su valor original.

NOTA: Si usamos el strict mode, podremos observar que se nos devuelve un error en cuanto tratamos de borrar la propiedad:

"use strict";
// ...
delete circle.radius; // property circle.radius is non-configurable and can't be deleted
// ...

Protegiendo un objeto

ECMAScript 5 proporciona además un nuevo método con el que controlar la ‘extensibilidad‘ de un objeto. Llamando a Object.preventExtensions( obj ), nos aseguramos de que no puedan añadirse más propiedades a un objeto específico:

var circle = {};
Object.preventExtensions( obj );
 
circle.radius = 4;
console.log( circle.radius ); // undefined

Igual que en el caso anterior, si utilizamos el modo estricto, se nos advierte del error:

// circle.radius is not extensible

Objetos Inmutables

Si prevenimos que un objeto no pueda ser extendido y fijamos sus atributos configurable y writable con un valor false, conseguimos entonces crear objetos inmutables cuyas propiedades no pueden modificarse. Esto puede resultar muy útil en aplicaciones cuyos objetos necesitamos que permanezcan inalterables reduciendo considerablemente los posibles errores.

En esta vía, ES5 ofrece un método interesante: Object.seal ( obj ).

Este método sella un objeto impidiendo que puedan añadirse nuevas propiedades convirtiendo todas las existentes en no configurables. Los valores de las propiedades originales pueden modificarse mientras que no se modifique directamente su atributo writable. Es decir, no podemos añadir ni borrar propiedades, pero si modificar aquellos valores que tengan su atributo writable con valor true:

var circle = { radius : 4 };
 
// Sealing the object
Object.seal( circle );
 
// We can not add a new property
circle.width = 10;
 
// But changing the property value still works
circle.radius = 10;
 
// We can not delete the property
delete circle.radius;
 
console.dir( circle ); // radius 10

NOTA: podemos comprobar en cualquier momento si un objeto ha sido sellado utilizando el método Object.isSealed( obj ).

Además del método anterior, si lo que pretendemos es congelar completamente un objeto, esto es, impedir que puedan añadirse, modificarse o borrarse propieades o cambiar el estado de alguno de sus atributos ( enumerable, configurable o writable), podemos utilizar Object.freeze( obj ).

Con este método, hacemos el objeto completamente inmutable:

var circle = { };
 
Object.defineProperty( circle, "radius", {
  value : 4,
  writable : false,
  configurable : false
})
 
// Freezing the Object
Object.freeze( circle );
 
// Now any changes will fail
circle.width = 10; // Silently does not add the property
circle.radius = 20; // Silently does nothing
 
console.log( circle.width ) // undefined;
console.log( circle.radius ) // 4;

Conclusión

Acercándonos más a un lenguaje OO puro, la nueva especificación ECMAScript 5 proporciona varios métodos y atributos nuevos con los que conseguir un nivel de control sin precedentes sobre nuestros objetos. Esto finalmente se traduce en una mayor flexibilidad del lenguaje para construir aplicaciones más grandes y complejas cara a navegadores e incluso aplicaciones de escritorio como nos está demostrando el proyecto GNOME.

Hasta hace poco, Internet Explorer 6 ha supuesto el freno al desarrollo de aplicaciones web por culpa de su arquitectura obsoleta. Sin embargo, con sus horas de vida contadas (literal) y una cuota de mercado en franco retroceso, podemos ir abriendo las puertas a soluciones modernas. A día de hoy, todos los navegadores recientes incluyen un amplio soporte al ES5, por lo que no hay razón para no trabajar con el nuevo estándar y todas sus posibilidades.

Más:

Aún no tenemos debug!
Ningún marciano se ha comunicado.

Deja un comentario

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