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
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"];
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;
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;
▍ 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");
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();
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"); }
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.
