JavaScript: explorar objetos

El material, cuya traducción publicamos hoy, está dedicado al estudio de objetos, una de las esencias clave de JavaScript. Está diseñado principalmente para desarrolladores principiantes que desean optimizar su conocimiento de los objetos.



Los objetos en JavaScript son colecciones dinámicas de propiedades que, además, contienen una propiedad "oculta" que es un prototipo del objeto. Las propiedades de los objetos se caracterizan por claves y valores. Comencemos la conversación sobre los objetos JS con claves.

Claves de propiedad de objeto


La clave de propiedad del objeto es una cadena única. Puede usar dos métodos para acceder a las propiedades: acceder a ellas durante un período y especificar la clave del objeto entre corchetes. Al acceder a las propiedades a través de un punto, la clave debe ser un identificador de JavaScript válido. Considere un ejemplo:

let obj = {  message : "A message" } obj.message //"A message" obj["message"] //"A message" 

Cuando intente acceder a una propiedad inexistente de un objeto, no aparecerá un mensaje de error, pero se devolverá el valor undefined :

 obj.otherProperty //undefined 

Al usar corchetes para acceder a las propiedades, puede usar claves que no sean identificadores de JavaScript válidos (por ejemplo, la clave puede ser una cadena que contenga espacios). Pueden tener cualquier valor que se pueda convertir en una cadena:

 let french = {}; french["merci beaucoup"] = "thank you very much"; french["merci beaucoup"]; //"thank you very much" 

Si los valores que no son cadenas se utilizan como claves, se convierten automáticamente en cadenas (utilizando, si es posible, el método toString() ):

 et obj = {}; //Number obj[1] = "Number 1"; obj[1] === obj["1"]; //true //Object let number1 = { toString : function() { return "1"; } } obj[number1] === obj["1"]; //true 

En este ejemplo, el objeto number1 se usa como clave. Al intentar acceder a una propiedad, se convierte a la línea 1 y el resultado de esta conversión se utiliza como clave.

Valores de propiedad del objeto


Las propiedades de los objetos pueden ser valores primitivos, objetos o funciones.

▍Objeto como valor de propiedad de un objeto


Los objetos se pueden colocar en otros objetos. Considere un ejemplo :

 let book = { title : "The Good Parts", author : {   firstName : "Douglas",   lastName : "Crockford" } } book.author.firstName; //"Douglas" 

Se puede usar un enfoque similar para crear espacios de nombres:

 let app = {}; app.authorService = { getAuthors : function() {} }; app.bookService = { getBooks : function() {} }; 

▍ Funciona como un valor de propiedad de objeto


Cuando una función se usa como un valor de propiedad de objeto, generalmente se convierte en un método de objeto. Dentro del método, para acceder al objeto actual, use la this .

Sin embargo, esta palabra clave puede tener diferentes significados, dependiendo de cómo se llamó la función. Aquí puede leer sobre situaciones en las que this pierde contexto.

La naturaleza dinámica de los objetos.


Los objetos en JavaScript, por su naturaleza, son entidades dinámicas. Puede agregarles propiedades en cualquier momento, lo mismo ocurre con la eliminación de propiedades:

 let obj = {}; obj.message = "This is a message"; //   obj.otherMessage = "A new message"; //    delete obj.otherMessage; //  

Objetos como matrices asociativas


Los objetos pueden considerarse como matrices asociativas. Las claves de matriz asociativas son los nombres de propiedad del objeto. Para acceder a la clave, no necesita mirar todas las propiedades, es decir, la operación de acceder a la clave de una matriz asociativa basada en un objeto se realiza en el tiempo O (1).

Prototipos de objetos


Los objetos tienen un enlace "oculto", __proto__ , que apunta a un objeto prototipo del cual el objeto hereda propiedades.

Por ejemplo, un objeto creado usando un objeto literal tiene un enlace a Object.prototype :

 var obj = {}; obj.__proto__ === Object.prototype; //true 

▍ Objetos vacíos


Como acabamos de ver, el objeto "vacío", {} , en realidad no está tan vacío, ya que contiene una referencia a Object.prototype . Para crear un objeto verdaderamente vacío, debe usar la siguiente construcción:

 Object.create(null) 

Gracias a esto, se creará un objeto sin prototipo. Tales objetos se usan generalmente para crear matrices asociativas.

▍ Cadena de prototipo


Los objetos prototipo pueden tener sus propios prototipos. Si intenta acceder a una propiedad de un objeto que no está en él, JavaScript intentará encontrar esta propiedad en el prototipo de este objeto, y si la propiedad deseada no está allí, se intentará encontrarla en el prototipo del prototipo. Esto continuará hasta que se encuentre la propiedad deseada, o hasta que se llegue al final de la cadena del prototipo.

Valores de tipo primitivo y envoltorios de objetos


JavaScript le permite trabajar con los valores de tipos primitivos como objetos, en el sentido de que el lenguaje le permite acceder a sus propiedades y métodos.

 (1.23).toFixed(1); //"1.2" "text".toUpperCase(); //"TEXT" true.toString(); //"true" 

Además, por supuesto, los valores de los tipos primitivos no son objetos.

Para organizar el acceso a las "propiedades" de los valores de los tipos primitivos, JavaScript, si es necesario, crea objetos envolventes que, después de ser innecesarios, se destruyen. El motor JS optimiza el proceso de creación y destrucción de objetos contenedor.

Los contenedores de objetos tienen valores de tipo numérico, de cadena y lógico. Los objetos de los tipos correspondientes están representados por las funciones de constructor Number , String y Boolean .

Prototipos Embebidos


Los objetos numéricos heredan propiedades y métodos del prototipo Number.prototype , que es el descendiente de Object.prototype :

 var no = 1; no.__proto__ === Number.prototype; //true no.__proto__.__proto__ === Object.prototype; //true 

El prototipo de los objetos de cadena es String.prototype . El prototipo de objetos booleanos es Boolean.prototype . El prototipo de matrices (que también son objetos) es Array.prototype .

Las funciones en JavaScript también son objetos que tienen un prototipo Function.prototype . Las funciones tienen métodos como bind() , apply() y call() .

Todos los objetos, funciones y objetos que representan valores de tipo primitivos (excepto los valores null e undefined ) heredan propiedades y métodos de Object.prototype . Esto lleva al hecho de que, por ejemplo, todos tienen un método toString() .

Extender objetos incrustados con polyfills


JavaScript facilita la extensión de objetos incrustados con nuevas características utilizando los llamados polyfills. Un polyfill es un fragmento de código que implementa características que no son compatibles con ningún navegador.

▍Uso de polyfills


Por ejemplo, hay un polyfill para el método Object.assign() . Le permite agregar una nueva función al Object si no está disponible en él.

Lo mismo se aplica al polyfill Array.from() , que, si el método from() no está en el objeto Array , lo equipa con este método.

▍ Polyfill y prototipos


Con la ayuda de polyfills, se pueden agregar nuevos métodos a los prototipos de objetos. Por ejemplo, el polyfill para String.prototype.trim() permite equipar todos los objetos de cadena con el método trim() :

 let text = "   A text "; text.trim(); //"A text" 

El polyfill para Array.prototype.find() permite equipar todas las matrices con el método find() . El polyfill para Array.prototype.findIndex() funciona de manera similar:

 let arr = ["A", "B", "C", "D", "E"]; arr.indexOf("C"); //2 

Herencia individual


El Object.create() permite crear nuevos objetos con un objeto prototipo dado. Este comando se usa en JavaScript para implementar un único mecanismo de herencia. Considere un ejemplo :

 let bookPrototype = { getFullTitle : function(){   return this.title + " by " + this.author; } } let book = Object.create(bookPrototype); book.title = "JavaScript: The Good Parts"; book.author = "Douglas Crockford"; book.getFullTitle();//JavaScript: The Good Parts by Douglas Crockford 

Herencia múltiple


El Object.assign() copia propiedades de uno o más objetos al objeto de destino. Se puede usar para implementar múltiples esquemas de herencia. Aquí hay un ejemplo :

 let authorDataService = { getAuthors : function() {} }; let bookDataService = { getBooks : function() {} }; let userDataService = { getUsers : function() {} }; let dataService = Object.assign({}, authorDataService, bookDataService, userDataService ); dataService.getAuthors(); dataService.getBooks(); dataService.getUsers(); 

Objetos inmutables


El Object.freeze() permite "congelar" un objeto. No puede agregar nuevas propiedades a dicho objeto. Las propiedades no se pueden eliminar ni se pueden cambiar sus valores. Al usar este comando, un objeto se vuelve inmutable o inmutable:

 "use strict"; let book = Object.freeze({ title : "Functional-Light JavaScript", author : "Kyle Simpson" }); book.title = "Other title";//: Cannot assign to read only property 'title' 

El Object.freeze() realiza el llamado "congelamiento superficial" de objetos. Esto significa que los objetos anidados en un objeto "congelado" pueden modificarse. Para llevar a cabo una "congelación profunda" de un objeto, debe "congelar" recursivamente todas sus propiedades.

Clonando objetos


Para crear clones (copias) de objetos, puede usar el Object.assign() :

 let book = Object.freeze({ title : "JavaScript Allongé", author : "Reginald Braithwaite" }); let clone = Object.assign({}, book); 

Este comando realiza una copia superficial de objetos, es decir, solo copia propiedades de nivel superior. Los objetos anidados resultan ser comunes para los objetos originales y sus copias.

Objeto literal


Los literales de objetos brindan a los desarrolladores una forma simple y directa de crear objetos:

 let timer = { fn : null, start : function(callback) { this.fn = callback; }, stop : function() {}, } 

Sin embargo, este método de crear objetos tiene desventajas. En particular, con este enfoque, todas las propiedades del objeto están disponibles públicamente, los métodos del objeto se pueden redefinir, no se pueden usar para crear nuevas instancias de los mismos objetos:

 timer.fn;//null timer.start = function() { console.log("New implementation"); } 

Método Object.create ()


Los dos problemas mencionados anteriormente se pueden resolver mediante el uso conjunto de los métodos Object.create() y Object.freeze() .

Aplicamos esta técnica a nuestro ejemplo anterior. Primero, cree un prototipo de timerPrototype prototipo congelado que contenga todos los métodos necesarios para varias instancias del objeto. Después de eso, cree un objeto que sea sucesor de timerPrototype :

 let timerPrototype = Object.freeze({ start : function() {}, stop : function() {} }); let timer = Object.create(timerPrototype); timer.__proto__ === timerPrototype; //true 

Si el prototipo está protegido de los cambios, el objeto que es su heredero no podrá cambiar las propiedades definidas en el prototipo. Ahora los métodos start() y stop() no se pueden anular:

 "use strict"; timer.start = function() { console.log("New implementation"); } //: Cannot assign to read only property 'start' of object 

La Object.create(timerPrototype) se puede usar para crear múltiples objetos con el mismo prototipo.

Función constructora


JavaScript tiene las llamadas funciones de constructor, que son "azúcar sintáctico" para realizar los pasos anteriores para crear nuevos objetos. Considere un ejemplo :

 function Timer(callback){ this.fn = callback; } Timer.prototype = { start : function() {}, stop : function() {} } function getTodos() {} let timer = new Timer(getTodos); 

Puede usar cualquier función como constructor. Se llama al constructor utilizando la new palabra clave. Un objeto creado usando una función constructora llamada FunctionConstructor recibirá un prototipo FunctionConstructor.prototype :

 let timer = new Timer(); timer.__proto__ === Timer.prototype; 

Aquí, para evitar un cambio en el prototipo, nuevamente, puede congelar el prototipo:

 Timer.prototype = Object.freeze({ start : function() {}, stop : function() {} }); 

▍ Palabra clave nueva


Cuando se ejecuta un comando de la forma new Timer() , se realizan las mismas acciones que la función newTimer() continuación:

 function newTimer(){ let newObj = Object.create(Timer.prototype); let returnObj = Timer.call(newObj, arguments); if(returnObj) return returnObj;   return newObj; } 

Aquí se crea un nuevo objeto, cuyo prototipo es Timer.prototype . Luego se llama a la función Timer , que establece los campos para el nuevo objeto.

Palabra clave de clase


ECMAScript 2015 introdujo una nueva forma de realizar las acciones anteriores, que es otro lote de "azúcar sintáctico". Estamos hablando de la palabra clave de class y las construcciones relacionadas asociadas con ella. Considere un ejemplo :

 class Timer{ constructor(callback){   this.fn = callback; } start() {} stop() {} } Object.freeze(Timer.prototype); 

Un objeto creado usando la palabra clave de class basada en una clase llamada ClassName tendrá el prototipo ClassName.prototype . Al crear un objeto basado en una clase, use la new palabra clave:

 let timer= new Timer(); timer.__proto__ === Timer.prototype; 

Usar clases no hace que los prototipos sean inmutables. Si es necesario, deberán "congelarse" de la misma manera que ya lo hicimos nosotros:

 Object.freeze(Timer.prototype); 

Herencia basada en prototipos


En JavaScript, los objetos heredan propiedades y métodos de otros objetos. Las funciones y clases de constructor son "azúcar sintáctico" para crear prototipos de objetos que contienen todos los métodos necesarios. Al usarlos, se crean nuevos objetos que son los herederos del prototipo, cuyas propiedades, específicas de una instancia en particular, se establecen utilizando la función de constructor o los mecanismos de clase.

Sería bueno si las funciones y clases de constructor pudieran hacer automáticamente prototipos inmutables.

Los puntos fuertes de la herencia de prototipos son los ahorros de memoria. El hecho es que un prototipo se crea solo una vez, después de lo cual todos los objetos creados sobre su base lo usan.

▍ El problema de la falta de mecanismos de encapsulación integrados


La plantilla de herencia prototipo no utiliza la separación de las propiedades de los objetos en privado y público. Todas las propiedades de los objetos están disponibles públicamente.

Por ejemplo, el Object.keys() devuelve una matriz que contiene todas las claves de propiedad del objeto. Se puede usar para iterar sobre todas las propiedades de un objeto:

 function logProperty(name){ console.log(name); //  console.log(obj[name]); //  } Object.keys(obj).forEach(logProperty); 

Hay un patrón que imita las propiedades privadas, que se basa en el hecho de que los desarrolladores no tendrán acceso a esas propiedades cuyos nombres comienzan con un guión bajo ( _ ):

 class Timer{ constructor(callback){   this._fn = callback;   this._timerId = 0; } } 

Características de la fábrica


Los objetos encapsulados en JavaScript se pueden crear utilizando funciones de fábrica. Se ve así:

 function TodoStore(callback){   let fn = callback;     function start() {},   function stop() {}     return Object.freeze({      start,      stop   }); } 

Aquí la variable fn es privada. Solo los métodos start() y stop() están disponibles públicamente. Estos métodos no pueden modificarse externamente. La palabra clave this no se usa aquí, por lo tanto, cuando se usa este método de creación de objetos, el problema de perder this contexto es irrelevante.

El comando return usa un objeto literal que contiene solo funciones. Además, estas funciones se declaran cerradas; comparten un estado común. Para congelar una API pública de un objeto, se Object.freeze() comando Object.freeze() ya conocido.

Aquí, en los ejemplos, usamos el objeto Timer . En este material puede encontrar su implementación completa.

Resumen


En JavaScript, los valores de tipos primitivos, objetos ordinarios y funciones se tratan como objetos. Los objetos tienen una naturaleza dinámica, se pueden usar como matrices asociativas. Los objetos son herederos de otros objetos. Las funciones y clases de constructor son "azúcar sintáctico", le permiten crear objetos basados ​​en prototipos. Puede usar el método Object.create() para organizar una herencia única y Object.create() para organizar una herencia múltiple. Puede usar funciones de fábrica para crear objetos encapsulados.

Estimados lectores! Si llegó a JavaScript desde otros idiomas, cuéntenos qué le gusta o qué no le gustan de los objetos JS, en comparación con la implementación de objetos en los idiomas que ya conoce.

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


All Articles