Detalles del objeto JavaScript

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) // => '101' (5    ) 

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 // => 'Arfat' 

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; // => 'Arfat' const dataObj = { name: 'Arfat', }; dataObj.name; // => 'Arfat' 

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); //        }, set id(value) {   this._id = value; } } obj.name = 'Arfat'; obj.name; // => 'ARFAT' obj.id = 5; obj.id; // => '101 

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'); /* { value: 5, writable: true, enumerable: true, configurable: true } */ 

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)   // => 23 } } obj.x = 23; 

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 = {}; // #1 Object.defineProperty(obj, 'id', { value: 42 }); // #2 console.log(obj); // => { } // #3 console.log(obj.id); // => 42 // #4 Object.defineProperty(obj, 'name', { value: 'Arfat', writable: false, enumerable: true, configurable: true }); // #5 console.log(obj.name); // => 'Arfat' // #6 obj.name = 'Arfat Salman' // #7 console.log(obj.name); // => 'Arfat' // (  'Arfat Salman') Object.defineProperty(obj, 'lastName', { value: 'Salman', enumerable: false, }); console.log(Object.keys(obj)); // => [ 'name' ] // #8 delete obj.id; // #9 console.log(obj.id); // => 42 //#10 Object.defineProperties(obj, { property1: {   value: 42,   writable: true }, property2: {} }); console.log(obj.property1) // => 42 

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); // => { id: 42 } 

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 // ( ) obj.name = 'Arfat'; // ( ) console.log(obj); // => { id: 42 } Object.isExtensible(obj); // => false Object.isSealed(obj); //=> true 

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 // ( ) obj.name = 'Arfat'; // ( ) console.log(obj); // => { id: 42 } Object.isExtensible(obj); // => false Object.isSealed(obj); //=> true Object.isFrozen(obj); // => true 

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?

Source: https://habr.com/ru/post/438794/


All Articles