El autor del material, cuya traducción publicamos hoy, dice que los objetos de JavaScript contienen muchas cosas, cuya existencia ni siquiera se puede sospechar, usándolas en el trabajo diario. Los objetos en JavaScript son muy fáciles de crear, convenientes para trabajar, parecen comprensibles y flexibles, y muchos programadores simplemente no piensan en el hecho de que los objetos en realidad no son tan simples.

NB: La información de la publicación en la práctica debe aplicarse con mucho cuidado y bajo la supervisión de colegas más experimentados.
Aquí hablamos de lo que está oculto en las profundidades de los objetos y discutimos las complejidades de trabajar con ellos.
Una vez que domine este material, sabrá las respuestas a las siguientes preguntas:
- ¿Cómo hacer que una propiedad de un objeto sea indeleble?
- ¿Qué son las propiedades con métodos de acceso y cuáles son sus características?
- ¿Cómo hacer que una propiedad sea inmutable u oculta?
- ¿Por qué algunas propiedades no son visibles en
for-in
bucles for-in
o en los resultados del método Object.keys()
, y algunas son visibles? - ¿Cómo "proteger" un objeto de modificaciones?
- Cómo entender un código similar al siguiente:
obj.id = 5; console.log(obj.id)
Tipos de propiedades de objeto
▍ Propiedades de almacenamiento de datos
Probablemente ha creado innumerables objetos que se parecen a esto:
const obj = { name: 'Arfat', id: 5 } obj.name
El
name
y las propiedades de
id
del objeto
obj
se denominan propiedades de
obj
datos o "Propiedades de datos". Estas son propiedades familiares que se encuentran constantemente en el código JavaScript. ¿Qué otros tipos de propiedades pueden tener los objetos?
▍ Propiedades con métodos de acceso
Estas propiedades también se conocen como getters y setters; también se encuentran en otros lenguajes de programación como C # o Python. Una propiedad con Accessor Property es una combinación de dos funciones:
get
y
set
.
Al declarar tales propiedades, en lugar de usar la construcción tradicional de tipo
:
, se usa la siguiente sintaxis:
const accessorObj = { get name() { return 'Arfat'; } }; accessorObj.name;
Eche un vistazo al objeto
accesorObj
y compárelo con el objeto
dataObj
. Aparentemente, ahora muestran el mismo comportamiento. Al describir el primer objeto, utilizamos la palabra clave
get
, seguida de la declaración de la función. Para acceder a una propiedad similar, aunque está representada por una función, no necesita poner paréntesis después del nombre de la propiedad para llamar a esta función. Es decir, un diseño como
accessorObj.name();
incorrecto
Cuando intenta acceder a la propiedad
accessorObj.name
, es decir, cuando intenta leerla, se ejecuta la función correspondiente y el valor devuelto se convierte en el valor de la propiedad de
name
.
Las funciones
get
se llaman getters; son responsables de obtener los valores. Si continuamos con nuestro ejemplo e intentamos cambiar el valor de la propiedad de
name
del objeto
accessorObj
, digamos, ejecutando el comando
accessorObj.name = 'New Person';
, entonces resulta que no pasará nada. El punto aquí es que la función de establecimiento no está asociada con la tecla de
name
. Dichas funciones le permiten personalizar el orden de asignación de nuevos valores a las propiedades de los objetos, cuyo acceso se organiza mediante getters.
Así es como se ve una declaración de objeto con un captador y definidor:
const accessorObj = { _name: 'Arfat', get name() { return this._name; }, set name(value) { this._name = value; } };
La función setter recibe lo que intentan asignar a la propiedad del objeto como parámetro. Ahora puede guardar algo en la propiedad del objeto. En este caso, creamos una propiedad "privada" del objeto
_name
. El primer carácter del nombre de dicha propiedad es un guión bajo, que no es más que una pista para el programador, que indica que esta propiedad está destinada a las necesidades internas del objeto. Además, trabajamos con él cuando accedemos a la propiedad del objeto de
name
, cuyo acceso está regulado por el captador y el definidor.
Al mismo tiempo, en la función getter, antes de devolver el valor de la propiedad
_name
, podemos modificarla.
Así es como podría verse:
const obj = { get name() { return this._name.toUpperCase(); }, set name(value) { this._name = value; }, get id() { return this._id.toString(2);
Este programa, por cierto, contiene la respuesta a una de las preguntas dadas al principio del artículo, que se refiere al análisis del código incomprensible a primera vista.
¿Por qué alguien necesitaría propiedades con métodos de acceso si puede trabajar de manera segura con propiedades ordinarias? Por ejemplo, pueden ser necesarios para registrar información sobre lecturas de propiedades o para almacenar un historial de cambios en los valores de las propiedades. Las propiedades con métodos de acceso nos brindan todas las posibilidades de procesar datos utilizando funciones y la simplicidad característica de trabajar con propiedades ordinarias. Lea más sobre el uso de tales propiedades
aquí .
¿Cómo distingue JavaScript entre propiedades ordinarias que almacenan datos de propiedades con métodos de acceso? Descubre esto.
Descriptores de propiedad de objeto
A primera vista, puede parecer que existe una correspondencia directa entre las claves y los valores almacenados en los objetos. Sin embargo, esto no es del todo cierto.
▍ Atributos de propiedad
Cada clave del objeto está asociada con un conjunto de atributos que determinan las características del valor asociado con esta clave. Estos atributos también se pueden considerar como metadatos que describen una
:
par de
:
.
Los atributos se utilizan para establecer y describir el estado de las propiedades de los objetos. El conjunto de atributos de propiedad se denomina descriptor. Hay seis atributos de propiedad:
[[Value]]
[[Get]]
[[Set]]
[[Writable]]
[[Enumerable]]
[[Configurable]]
¿Por qué los nombres de atributos de propiedad en esta lista están encerrados en la construcción
[[]]
? Los corchetes dobles indican que se trata de entidades utilizadas por los mecanismos internos del lenguaje. Un programador de JS no puede acceder a estas propiedades directamente. Para influir en ellos, se utilizan métodos apropiados.
Considere la siguiente imagen, tomada
desde aquí , en la que puede ver el objeto y los atributos de sus propiedades.
Objeto y atributos de sus propiedades.Nuestro objeto tiene 2 claves:
x
e
y
. Además, un conjunto de atributos está asociado con cada uno de ellos.
¿Cómo, usando JavaScript, obtener información sobre el objeto, similar a los mostrados en la figura anterior? Puede usar la función
Object.getOwnPropertyDescriptor()
para esto. Toma un objeto y el nombre de su propiedad, y luego devuelve un objeto que contiene los atributos de esta propiedad. Aquí hay un ejemplo:
const object = { x: 5, y: 6 }; Object.getOwnPropertyDescriptor(object, 'x');
Cabe señalar que la composición de los atributos de una propiedad particular depende de su tipo. No se encuentran los seis atributos de la misma propiedad.
- Si estamos hablando de propiedades con datos, entonces solo tendrán los atributos
[[Value]]
, [[Writable]]
, [[Enumerable]]
y [[Configurable]]
. - Las propiedades con métodos de acceso, en lugar de los atributos
[[Value]]
y [[Writable]]
, tienen los atributos [[Get]]
y [[Set]]
.
▍ [[Valor]]
Este atributo almacena lo que se devuelve al intentar obtener el valor de propiedad de un objeto. Es decir, si en el ejemplo anterior usamos una construcción de la forma
object.x
, obtenemos lo que está almacenado en el atributo
[[Value]]
. Lo mismo sucede cuando intentas leer las propiedades de un objeto usando corchetes.
▍ [[Obtener]]
Este atributo almacena una referencia a una función, que es una propiedad getter. Esta función se llama sin argumentos cuando se intenta leer el valor de una propiedad.
▍ [[Set]]
Aquí es donde se almacena el enlace a la función declarada al crear la propiedad setter. Se llama con un argumento que representa el valor que intentaron asignar a la propiedad, es decir, se llama durante cada operación de asignar un nuevo valor a una propiedad.
const obj = { set x(val) { console.log(val)
En este ejemplo, el lado derecho de la expresión se pasa como un argumento
val
a la función setter.
Aquí está el código que demuestra el uso de setters y getters.
▍ [[Escribible]]
Este atributo tiene un valor booleano. Indica si el valor de la propiedad puede sobrescribirse o no. Si
false
se almacena aquí, los intentos de cambiar el valor de la propiedad fallarán.
▍ [[Enumerable]]
Aquí también se almacena un valor lógico. Este atributo controla la emisión de la propiedad
for-in
bucles
for-in
. Si se establece en
true
, entonces será posible trabajar con la propiedad usando tales ciclos.
▍ [[Configurable]]
Este atributo también está representado por un valor booleano. Esto es lo que sucede si se almacena
false
en él:
- La propiedad no se puede eliminar.
- No puede convertir propiedades que almacenan datos en propiedades con métodos de acceso, y viceversa. Los intentos de realizar tales transformaciones no conducirán a nada.
- No se permitirá cambiar los valores de los atributos de propiedad. Es decir, los valores actuales de los atributos
[[Enumerable]]
, [[Configurable]]
, [[Get]]
y [[Set]]
no se modificarán.
El efecto de establecer este atributo en
false
también depende del tipo de propiedad. Este atributo, además de los efectos anteriores sobre las propiedades, actúa sobre ellos y así:
- Si esta es una propiedad que almacena datos, entonces el atributo
[[Writable]]
solo se puede cambiar de true
a false
. - Hasta que el atributo
[[Writable]]
se establezca en false
, el atributo [[Value]]
se puede cambiar. Pero después de que los atributos [[Writable]]
y [[Configurable]]
se establezcan en false
, la propiedad resultará no escribible, indeleble e inmutable.
Trabajar con descriptores.
Ahora que nos hemos familiarizado con los atributos, nos preguntaremos cómo podemos influir en ellos. JavaScript tiene funciones especiales para trabajar con descriptores de propiedad. Hablemos de ellos.
▍ Método Object.getOwnPropertyDescriptor ()
Ya hemos conocido este método. Al tomar un objeto y el nombre de su propiedad, devuelve
undefined
o un objeto con un descriptor de propiedad.
▍ Método Object.defineProperty ()
Este es un método de
Object
estático que le permite agregar propiedades a objetos o cambiar propiedades existentes. Se necesitan tres argumentos: un objeto, un nombre de propiedad y un objeto con un descriptor. Este método devuelve un objeto modificado. Considere un ejemplo:
const obj = {};
Se puede ejecutar en Node.js. El código resultó ser bastante grande, pero, de hecho, es bastante simple. Lo analizaremos, enfocándonos en los comentarios de la forma
// #n
.
En el fragmento
#1
usamos la función
defineProperty
, pasándole el objeto
obj
, el nombre de la propiedad
id
y un objeto descriptor que contiene solo la propiedad
value
, lo que indica que
42
se escribirá en el atributo
[[Value]]
. Recuerde que si no pasa valores para atributos como
[[Enumerable]]
o
[[Configurable]]
en este objeto, se establecerán en
false
de forma predeterminada. En este caso, los atributos
[[Writable]]
,
[[Enumerable]]
y
[[Configurable]]
propiedad
id
se establecen en
false
.
En el lugar marcado como
#2
, estamos tratando de mostrar una representación de cadena del objeto en la consola. Como su propiedad
id
no es enumerable, no se mostrará. Además, la propiedad existe, lo que demuestra su conclusión exitosa mediante el comando
#3
.
Al crear un objeto (fragmento
#4
), definimos una lista completa de atributos. En particular, establezca
[[Writable]]
en
false
.
Con los comandos
#5
y
#7
mostramos el valor de la propiedad de
name
. Pero entre ellos (fragmento
#6
) intentamos cambiar este valor. Esta operación no cambió el valor de la propiedad porque su atributo
[[Writable]]
está establecido en
false
. Como resultado, ambos comandos envían lo mismo a la consola.
El comando
#8
es un intento de eliminar la propiedad
id
. Recuerde que su atributo
[[Configurable]]
está establecido en
false
, lo que significa que no se puede eliminar. Esto lo demuestra el equipo
#9
.
El fragmento
#10
muestra el uso de la función
Object.defineProperties () . Funciona igual que la función
defineProperty()
, pero permite, en una llamada, afectar varias propiedades del objeto, mientras que
defineProperty()
funciona con una sola propiedad del objeto.
Protección de objetos
De vez en cuando, el desarrollador necesita proteger los objetos de la interferencia externa. Por ejemplo, dada la flexibilidad de JavaScript, es muy fácil cambiar por error las propiedades de un objeto que no debería cambiar. Hay tres formas principales de proteger objetos.
▍ Método Object.preventExtensions ()
El método
Object.preventExtensions()
evita que el objeto se expanda, es decir, que le agregue nuevas propiedades. Toma un objeto y lo hace no expandible. Tenga en cuenta que puede eliminar propiedades de dicho objeto. Considere un ejemplo:
const obj = { id: 42 }; Object.preventExtensions(obj); obj.name = 'Arfat'; console.log(obj);
Para saber si un objeto no es expandible, puede usar el método
Object.isExtensible()
. Si devuelve
true
, puede agregar nuevas propiedades al objeto.
▍ Método Object.seal ()
El método
seal()
parece "sellar" objetos. Esto es de lo que estamos hablando:
- Su uso evita que se agreguen nuevas propiedades al objeto (en esto es similar a
Object.preventExtensions()
). - Hace que todas las propiedades existentes de un objeto no sean configurables.
- Los valores de las propiedades existentes, si su atributo
[[Writable]]
no está establecido en false
, se pueden cambiar.
Como resultado, resulta que este método evita la adición de nuevas propiedades al objeto y la eliminación de las propiedades existentes en él.
Considere un ejemplo:
const obj = { id: 42 }; Object.seal(obj); delete obj.id
Para verificar si el objeto está "sellado" o no, puede usar el método
Object.isSealed()
.
▍ Método Object.freeze ()
El método
freeze()
permite "congelar" objetos, equipándolos con el mayor nivel de protección posible en JavaScript. Así es como funciona:
Object.seal()
un objeto usando Object.seal()
.- Prohibe completamente la modificación de cualquier propiedad existente del objeto.
- Prohíbe modificar descriptores de propiedad.
Aquí hay un ejemplo:
const obj = { id: 42 }; Object.freeze(obj); delete obj.id
Puede verificar si el objeto está "congelado" utilizando el método
Object.isFrozen()
.
▍ Descripción general de los métodos utilizados para proteger objetos
Es importante tener en cuenta que los métodos anteriores utilizados para proteger objetos afectan solo sus propiedades que no son objetos.
Aquí hay una tabla resumen sobre los métodos considerados para proteger objetos, que se toma
de aquí .
| Creación de propiedad
| Lectura de la propiedad
| Sobrescritura de propiedad
| Remoción de propiedad
|
Object.freeze()
| - | +
| - | - |
Object.seal()
| - | +
| +
| - |
Object.preventExtensions()
| - | +
| +
| +
|
Resumen
Dada la frecuencia con la que se usan los objetos en JavaScript, es importante que cada desarrollador sepa cómo están organizados. Esperamos que lo que aprendió al leer este material le sea útil. Además, ahora conoce las respuestas a las preguntas enumeradas al principio del artículo.
Estimados lectores! ¿Cómo se protegen los objetos JavaScript?