Como sabemos, los objetos pueden almacenar propiedades.
Hasta ahora, para nosotros una propiedad era un simple par "clave-valor". Pero la propiedad de un objeto es en realidad algo más flexible y poderoso.
En este capítulo estudiaremos opciones de configuración adicionales y en el siguiente veremos cómo convertirlas de forma invisible en funciones getter/setter.
Las propiedades de los objetos, además de un value
, tienen tres atributos especiales (los llamados "banderas"):
writable
: si es true
, el valor se puede cambiar; de lo contrario, es de solo lectura.
enumerable
: si es true
, se enumera en bucles; de lo contrario, no se enumera.
configurable
: si es true
, la propiedad se puede eliminar y estos atributos se pueden modificar; de lo contrario, no.
No los vimos todavía, porque generalmente no aparecen. Cuando creamos una propiedad “de la forma habitual”, todas son true
. Pero también podemos cambiarlos en cualquier momento.
Primero, veamos cómo conseguir esas banderas.
El método Object.getOwnPropertyDescriptor permite consultar la información completa sobre una propiedad.
La sintaxis es:
let descriptor = Object.getOwnPropertyDescriptor(obj, nombrePropiedad);
obj
El objeto del que se va a obtener información.
propertyName
El nombre de la propiedad.
El valor devuelto es el llamado objeto "descriptor de propiedad": contiene el valor y todas las banderas.
Por ejemplo:
dejar usuario = { nombre: "Juan" }; let descriptor = Object.getOwnPropertyDescriptor(usuario, 'nombre'); alerta (JSON.stringify (descriptor, nulo, 2)); /* descriptor de propiedad: { "valor": "Juan", "escribible": verdadero, "enumerable": verdadero, "configurable": verdadero } */
Para cambiar las banderas, podemos usar Object.defineProperty.
La sintaxis es:
Object.defineProperty(obj, nombre de propiedad, descriptor)
obj
, propertyName
El objeto y su propiedad para aplicar el descriptor.
descriptor
Objeto descriptor de propiedad a aplicar.
Si la propiedad existe, defineProperty
actualiza sus indicadores. De lo contrario, crea la propiedad con el valor y las banderas dados; en ese caso, si no se proporciona una bandera, se supone false
.
Por ejemplo, aquí se crea un name
de propiedad con todos los indicadores falsos:
dejar usuario = {}; Object.defineProperty(usuario, "nombre", { valor: "Juan" }); let descriptor = Object.getOwnPropertyDescriptor(usuario, 'nombre'); alerta (JSON.stringify (descriptor, nulo, 2)); /* { "valor": "Juan", "escribible": falso, "enumerable": falso, "configurable": falso } */
Compárelo con user.name
“creado normalmente” arriba: ahora todas las banderas son falsas. Si eso no es lo que queremos, será mejor que los establezcamos en true
en descriptor
.
Ahora veamos los efectos de las banderas con un ejemplo.
Hagamos que user.name
no se pueda escribir (no se pueda reasignar) cambiando el indicador writable
:
dejar usuario = { nombre: "Juan" }; Object.defineProperty(usuario, "nombre", { grabable: falso }); nombre.usuario = "Pete"; // Error: No se puede asignar a la propiedad de solo lectura 'nombre'
Ahora nadie puede cambiar el nombre de nuestro usuario, a menos que aplique su propia defineProperty
para anular la nuestra.
Los errores aparecen sólo en modo estricto
En modo no estricto, no se producen errores al escribir en propiedades que no se pueden escribir y demás. Pero la operación todavía no tendrá éxito. Las acciones que violan la bandera simplemente se ignoran silenciosamente en casos no estrictos.
Este es el mismo ejemplo, pero la propiedad se crea desde cero:
dejar usuario = { }; Object.defineProperty(usuario, "nombre", { valor: "Juan", // para nuevas propiedades necesitamos enumerar explícitamente lo que es verdadero enumerable: verdadero, configurable: verdadero }); alerta(nombre.usuario); // John nombre.usuario = "Pete"; // Error
Ahora agreguemos un toString
personalizado al user
.
Normalmente, un toString
integrado para objetos no es enumerable, no aparece en for..in
. Pero si agregamos nuestro propio toString
, entonces, de forma predeterminada, aparece en for..in
, así:
dejar usuario = { nombre: "Juan", toString() { devolver este.nombre; } }; // De forma predeterminada, se enumeran nuestras dos propiedades: para (dejar ingresar usuario) alerta (clave); // nombre, toString
Si no nos gusta, podemos configurar enumerable:false
. Entonces no aparecerá en un bucle for..in
, como el integrado:
dejar usuario = { nombre: "Juan", toString() { devolver este.nombre; } }; Object.defineProperty(usuario, "toString", { enumerable: falso }); // Ahora nuestro toString desaparece: para (dejar ingresar usuario) alerta (clave); // nombre
Las propiedades no enumerables también están excluidas de Object.keys
:
alerta(Objeto.claves(usuario)); // nombre
El indicador no configurable ( configurable:false
) a veces está preestablecido para objetos y propiedades integrados.
Una propiedad no configurable no se puede eliminar, sus atributos no se pueden modificar.
Por ejemplo, Math.PI
no se puede escribir, no se puede enumerar ni se puede configurar:
let descriptor = Object.getOwnPropertyDescriptor(Matemáticas, 'PI'); alerta (JSON.stringify (descriptor, nulo, 2)); /* { "valor": 3.141592653589793, "escribible": falso, "enumerable": falso, "configurable": falso } */
Por lo tanto, un programador no puede cambiar el valor de Math.PI
ni sobrescribirlo.
Matemáticas.PI = 3; // Error, porque tiene escritura: false // eliminar Math.PI tampoco funcionará
Tampoco podemos cambiar Math.PI
para que se writable
nuevamente:
// Error, debido a configurable: falso Object.defineProperty(Math, "PI", {escribible: verdadero});
No hay absolutamente nada que podamos hacer con Math.PI
Hacer que una propiedad no sea configurable es un camino de sentido único. No podemos volver a cambiarlo con defineProperty
.
Tenga en cuenta: configurable: false
evita cambios en los indicadores de propiedad y su eliminación, al tiempo que permite cambiar su valor.
Aquí user.name
no es configurable, pero aún podemos cambiarlo (ya que se puede escribir):
dejar usuario = { nombre: "Juan" }; Object.defineProperty(usuario, "nombre", { configurable: falso }); nombre.usuario = "Pete"; // funciona bien eliminar nombre de usuario; // Error
Y aquí hacemos que user.name
sea una constante "sellada para siempre", al igual que el Math.PI
integrado:
dejar usuario = { nombre: "Juan" }; Object.defineProperty(usuario, "nombre", { grabable: falso, configurable: falso }); // no podremos cambiar el nombre de usuario ni sus indicadores // todo esto no funcionará: nombre.usuario = "Pete"; eliminar nombre de usuario; Object.defineProperty(usuario, "nombre", { valor: "Pete" });
El único cambio de atributo posible: escribible verdadero → falso
Hay una pequeña excepción sobre el cambio de banderas.
Podemos cambiar writable: true
a false
para una propiedad no configurable, evitando así la modificación de su valor (para agregar otra capa de protección). Aunque no al revés.
Existe un método Object.defineProperties(obj, descriptores) que permite definir muchas propiedades a la vez.
La sintaxis es:
Objeto.defineProperties(obj, { propiedad1: descriptor1, prop2: descriptor2 //... });
Por ejemplo:
Objeto.defineProperties(usuario, { nombre: {valor: "Juan", escribible: falso}, apellido: { valor: "Smith", escribible: falso }, //... });
Entonces, podemos establecer muchas propiedades a la vez.
Para obtener todos los descriptores de propiedades a la vez, podemos usar el método Object.getOwnPropertyDescriptors(obj).
Junto con Object.defineProperties
se puede utilizar como una forma "con reconocimiento de banderas" de clonar un objeto:
let clone = Object.defineProperties({}, Object.getOwnPropertyDescriptors(obj));
Normalmente cuando clonamos un objeto, usamos una asignación para copiar propiedades, como esta:
para (dejar ingresar usuario) { clonar[clave] = usuario[clave] }
…Pero eso no copia banderas. Entonces, si queremos un clon "mejor", entonces se prefiere Object.defineProperties
.
Otra diferencia es que for..in
ignora las propiedades simbólicas y no enumerables, pero Object.getOwnPropertyDescriptors
devuelve todos los descriptores de propiedades, incluidos los simbólicos y no enumerables.
Los descriptores de propiedad funcionan a nivel de propiedades individuales.
También existen métodos que limitan el acceso a todo el objeto:
Objeto.preventExtensions(obj)
Prohíbe la adición de nuevas propiedades al objeto.
Objeto.sello(obj)
Prohíbe agregar/eliminar propiedades. Conjuntos configurable: false
para todas las propiedades existentes.
Objeto.congelar(obj)
Prohíbe agregar/eliminar/cambiar propiedades. Conjuntos configurable: false, writable: false
para todas las propiedades existentes.
Y también hay pruebas para ellos:
Objeto.isExtensible(obj)
Devuelve false
si está prohibido agregar propiedades; en caso contrario, true
.
Objeto.isSealed(obj)
Devuelve true
si está prohibido agregar o eliminar propiedades y todas las propiedades existentes son configurable: false
.
Objeto.isFrozen(obj)
Devuelve true
si está prohibido agregar/eliminar/cambiar propiedades y todas las propiedades actuales son configurable: false, writable: false
.
Estos métodos rara vez se utilizan en la práctica.