Skip to content
This page has been auto-translated and may contain errors.View in English

Objetos

docs.scrimba.com

Imagina que estás describiendo a un usuario: un nombre, un correo electrónico, una edad, si ha iniciado sesión. Podrías distribuir eso en cuatro variables separadas, pero pertenecen juntas, y mantenerlas separadas significa pasar cuatro cosas cada vez que hablas de "el usuario". Un objeto te permite reunir todo en un valor, donde cada parte tiene un nombre que puedes buscar. La mayoría de los datos que manejarás, un usuario, un producto, un carrito, llegan con exactamente esta forma.

Crear un objeto

Un objeto es una colección de valores nombrados, escrita con llaves. Dentro, enumeras pares clave: valor, separados por comas. La clave es el nombre; el valor es lo que contiene:

js
const user = {
  name: "Carlos",
  age: 27,
};

console.log(user.name); // Carlos

Lees un valor con un punto y la clave: user.name. Esto se llama notación de punto, y es lo que usarás la mayoría de las veces. La clave a la izquierda, el valor que almacena a la derecha.

Hay una segunda forma de leer un valor, usando corchetes con la clave como una cadena:

js
console.log(user["name"]); // Carlos

La mayoría de las veces la notación de punto se lee más limpia, así que úsala. Los corchetes tienen su lugar cuando la clave no es una palabra simple (digamos que tiene un espacio) o cuando no sabes la clave de antemano y está en otra variable.

Un objeto agrupa valores relacionados bajo claves de cadena, escritas como pares clave: valor dentro de llaves. Donde un array contiene una lista ordenada a la que accedes por posición, un objeto contiene valores a los que accedes por nombre:

js
const user = {
  name: "Carlos",
  age: 27,
  isActive: true,
};

console.log(user.name); // Carlos
console.log(user["age"]); // 27

Hay dos formas de acceder. La notación de punto (user.name) es la predeterminada: más corta y clara. La notación de corchetes (user["age"]) hace lo mismo pero toma la clave como una cadena, lo que desbloquea dos cosas que la notación de punto no puede hacer.

La forma de corchetes es requerida cuando la clave no es un identificador válido, por ejemplo cuando contiene un espacio o comienza con un dígito, y cuando la clave es dinámica, está en una variable en lugar de ser escrita literalmente:

js
const field = "name";
console.log(user[field]); // Carlos, lee la clave contenida en `field`
console.log(user.field); // undefined, busca una clave literal llamada "field"

Esa diferencia confunde a la gente: user[field] usa el valor de field, mientras que user.field busca una clave deletreada field. Leer una clave que no existe devuelve undefined en lugar de un error.

Un objeto es la estructura central de JavaScript para agrupar valores bajo claves. Las claves son cadenas (o Symbols, que puedes dejar de lado por ahora); un valor puede ser cualquier cosa, incluyendo otro objeto o una función. Lo escribes como pares clave: valor en llaves:

js
const user = {
  name: "Carlos",
  age: 27,
  isActive: true,
};

console.log(user.name); // Carlos
console.log(user["age"]); // 27

La notación de punto es el predeterminado idiomático. La notación de corchetes es la misma búsqueda con la clave calculada como una expresión, por lo que es requerida en exactamente dos casos: cuando la clave no es un identificador válido (user["first name"], user["2fa"]), y cuando la clave es dinámica:

js
const field = "name";
console.log(user[field]); // Carlos, evalúa `field` a "name" primero

Hay dos comportamientos que vale la pena recordar. Leer una clave faltante devuelve undefined, nunca lanza un error, así que un error tipográfico falla silenciosamente en lugar de hacerlo ruidosamente. Y las claves siempre son cadenas bajo el capó: user[27] y user["27"] acceden al mismo lugar, porque el número se coerciona a una clave de cadena. Esa coerción es una fuente común de confusión cuando las claves de objeto y los índices de array comienzan a parecerse.

js
const product = {
  title: "Lámpara de escritorio",
  price: 40,
  inStock: true,
};

console.log(`${product.title} costs ${product.price}`); // Lámpara de escritorio costs 40

Un objeto es un valor como cualquier otro. Puedes almacenarlo en una const, ponerlo en un array, pasarlo a una función y devolverlo. Las claves dan nombres a sus partes, que es el punto completo: product.price dice lo que es, donde un 40 simple sentado en una variable no lo hace.

JunoCrear un objeto Un objeto reúne valores relacionados bajo nombres, escritos como pares clave: valor en llaves. Lee un valor con un punto y la clave, como user.name. Recurre a la forma de corchetes solo cuando la clave tiene un carácter impar en ella o vive en una variable.
JunoCrear un objeto Un objeto accede a valores por nombre donde un array los accede por posición. La notación de punto es tu predeterminada; la notación de corchetes toma la clave como una cadena, así que úsala para claves que no son palabras simples o que viven en una variable. Recuerda que user[field] lee la clave almacenada en field, mientras que user.field busca una clave literal field, y una clave faltante vuelve como undefined en lugar de erroring.
JunoCrear un objeto Las claves son cadenas bajo el capó, así que user[27] y user["27"] golpean el mismo lugar, y una clave faltante devuelve undefined en lugar de lanzar un error. La notación de punto es idiomática; la notación de corchetes es la misma búsqueda con una clave calculada, necesaria para claves no identificadores y dinámicas. Ese undefined silencioso en un error tipográfico es conveniente hasta que oculta un bug.

Cambiar un objeto

Los objetos no son fijos una vez que los haces. Puedes agregar una nueva clave, cambiar una existente o eliminar una completamente. Para agregar o actualizar, asigna a la clave:

js
const user = {
  name: "Carlos",
};

user.age = 30; // agrega una nueva clave
user.name = "Carlos González"; // actualiza la existente

console.log(user); // { name: "Carlos González", age: 30 }

Si la clave ya existe, asignarle reemplaza el valor. Si no existe, se crea. Para eliminar una clave, usa delete:

js
delete user.age;
console.log(user); // { name: "Carlos González" }

Aquí está la parte que sorprende a la gente. Un objeto const aún puede tener sus propiedades cambiadas. const significa que el nombre user no puede apuntar a un objeto diferente, pero el objeto al que apunta es juego justo:

js
const cart = { total: 0 };
cart.total = 25; // bien, cambiando lo que hay dentro
// cart = { total: 25 }; // esto daría error, reasignando el nombre

Los objetos son mutables: agregas, actualizas y eliminas claves después de la creación. Asignar a una clave la crea si es nueva y la sobrescribe si existe. Eliminar una clave es el trabajo de delete:

js
const user = { name: "Carlos" };

user.age = 30; // agregar
user.age = 31; // actualizar
delete user.age; // eliminar

console.log(user); // { name: "Carlos" }

Lo que atrapa a la gente es const. const bloquea el nombre, no el contenido. El binding no puede ser reasignado a un nuevo objeto, pero el objeto que contiene sigue siendo totalmente editable:

js
const cart = { total: 0, items: 0 };
cart.total = 25; // permitido, mutando el objeto
cart.items += 1; // permitido
// cart = {}; // TypeError, reasignando un binding const

Por eso const es el predeterminado sensato incluso para objetos que planeabas cambiar. Casi nunca quieres reapuntar la variable, y const te detiene haciéndolo accidentalmente mientras deja el objeto mismo abierto.

Los objetos son mutables. La asignación a una clave la hace upsert (crea o sobrescribe), y delete la elimina:

js
const user = { name: "Carlos", age: 30 };

user.role = "admin"; // agregar
delete user.age; // eliminar

console.log(user); // { name: "Carlos", role: "admin" }

La distinción const es la que internalizar, porque realmente se trata de qué protege const. const congela el binding, nunca el valor. El nombre no puede ser reasignado; el objeto al que hace referencia sigue siendo abierto:

js
const cart = { total: 0 };
cart.total = 25; // bien, mutando el objeto referenciado
// cart = { total: 25 }; // TypeError: Assignment to constant variable

Si realmente quieres el objeto mismo bloqueado, Object.freeze(obj) previene agregar, cambiar o eliminar claves, aunque es un congelamiento superficial (los objetos anidados siguen siendo mutables) y silenciosamente no hace nada en código no estricto en lugar de erroring. En la práctica rara vez lo necesitas. El hábito que vale la pena mantener es const por predeterminado: señala que el binding es estable y bloquea la reasignación accidental, que es el error que realmente ocurre, mientras deja la mutación normal disponible.

JunoCambiar un objeto Asigna a una clave para agregarla o actualizarla, y usa delete para eliminar una. La sorpresa es const: te detiene apuntando user a un objeto diferente, pero aún puedes cambiar lo que hay dentro de uno que contiene. Me tomó un tiempo dejar de esperar que const bloqueara todo.
JunoCambiar un objeto La asignación agrega o sobrescribe una clave, delete elimina una. const bloquea el nombre, no el contenido, así que un objeto const sigue siendo totalmente editable mientras que la variable misma no puede ser reapuntada. Esa es exactamente la razón por la que const es un buen predeterminado incluso para objetos que intentas cambiar.
JunoCambiar un objeto La asignación hace upsert de una clave, delete elimina una, y const protege el binding en lugar del valor, así que el objeto referenciado sigue siendo mutable. Si debes bloquear el objeto, Object.freeze lo hace superficialmente y falla silenciosamente fuera del modo estricto. Recurre a él raramente; const por defecto ya detiene el error de reasignación que realmente ocurre.

Métodos y this

Un valor en un objeto puede ser una función. Cuando lo es, se llama método: algo que el objeto puede hacer, en lugar de un hecho que almacena. Lo llamas con el mismo punto, más paréntesis:

js
const user = {
  name: "Carlos",
  greet() {
    return `Hi, I am ${this.name}`;
  },
};

console.log(user.greet()); // Hi, I am Carlos

La nueva palabra dentro es this. Cuando llamas a user.greet(), this significa "el objeto en el que se llamó este método", que aquí es user. Así que this.name lee la clave name del mismo objeto. Así es como un método accede a los valores de su propio objeto sin que tengas que pasarlos.

Cuando una función se almacena como un valor en un objeto, es un método. Los métodos permiten que un objeto lleve comportamiento junto con sus datos, y acceden a esos datos a través de this:

js
const cart = {
  items: ["lamp", "desk"],
  total: 65,
  summary() {
    return `${this.items.length} items, ${this.total} total`;
  },
};

console.log(cart.summary()); // 2 items, 65 total

La regla para this que cubre casi todo lo que encontrarás: this es el objeto a la izquierda del punto en el momento de la llamada. Llama a cart.summary() y this es cart, así que this.items y this.total leen desde el mismo objeto.

La frase "en el momento de la llamada" importa. this no se fija cuando escribes el método, se decide por cómo se llama el método. Llámalo como cart.summary() y this es cart; extrae la función y llámala por su cuenta y this ya no apunta a cart. Encontrarás ese límite después, pero para métodos llamados con un punto, this se comporta exactamente como esperarías.

Una función almacenada en un objeto es un método, y accede al estado propio del objeto a través de this. La regla de binding que importa en la práctica: this se establece por cómo se llama una función, no dónde se define. Para una llamada normal de método, this es el objeto a la izquierda del punto:

js
const cart = {
  total: 65,
  discount: 0.1,
  finalPrice() {
    return this.total * (1 - this.discount);
  },
};

console.log(cart.finalPrice()); // 58.5

Porque el binding es dinámico, separar el método lo rompe: const fn = cart.finalPrice; fn(); pierde el contexto cart y this ya no apunta donde quieres.

El gotcha práctico son las funciones flecha como métodos. Una flecha no obtiene su propio this; captura el this del alcance circundante donde se definió. Así que una flecha utilizada como método lee el this externo (al nivel superior, no el objeto) en lugar del objeto en el que vive:

js
const user = {
  name: "Carlos",
  greet: () => `Hi, I am ${this.name}`, // incorrecto: `this` no es `user`
  greetOk() {
    return `Hi, I am ${this.name}`; // correcto: abreviatura de método
  },
};

La regla a llevar: usa abreviatura de método (greet() {}) cuando la función necesita que this signifique el objeto, y reserva las flechas para callbacks donde realmente quieres el this circundante, que es casi todo lo demás.

JunoMétodos y this Una función almacenada en un objeto es un método, llamado con un punto y paréntesis como user.greet(). Dentro, this significa el objeto en el que se llamó el método, así que this.name lee el propio name de ese objeto. Así es como un método accede a los valores de su propio objeto.
JunoMétodos y this Un método es una función viviendo en un objeto, y this es el objeto a la izquierda del punto cuando lo llamas. Así que cart.summary() hace que this signifique cart. La trampa es que this se decide en el momento de la llamada, no cuando escribes el método, lo que importa en el momento en que extraes un método de su objeto.
JunoMétodos y thisthis se vincula por cómo se llama una función, no dónde vive, así que extraer un método pierde su objeto. Usa abreviatura de método cuando necesites que this signifique el objeto, y mantén funciones flecha para callbacks donde quieres el this circundante, ya que una flecha captura el externo en su lugar. Esa mezcla de flecha como método es una clásica, y falla silenciosamente.

Objetos y arrays juntos

La mayoría de los datos reales es una lista de objetos: muchos usuarios, muchos productos, cada objeto un registro con las mismas claves. Pones objetos dentro de un array:

js
const products = [
  { title: "Lámpara de escritorio", price: 40 },
  { title: "Cuaderno", price: 6 },
  { title: "Bolígrafo", price: 3 },
];

console.log(products[0].title); // Lámpara de escritorio

Accedes a un objeto por su posición (products[0]), luego a uno de sus campos con un punto (.title). Encadenado, products[0].title lee el título del primer producto.

Para leer un campo de cada objeto, itera sobre el array:

js
for (const product of products) {
  console.log(product.title);
}
// Lámpara de escritorio
// Cuaderno
// Bolígrafo

Cada turno del bucle, product es un objeto de la lista, y product.title lee su título. Esta forma, un array de objetos que iteras, es la mayoría del trabajo de datos que harás.

Un array de objetos es la forma de la mayoría de los datos reales: cada objeto un registro, cada uno compartiendo las mismas claves. Combina acceso a array con acceso a punto para alcanzar cualquier campo único:

js
const products = [
  { title: "Lámpara de escritorio", price: 40 },
  { title: "Cuaderno", price: 6 },
  { title: "Bolígrafo", price: 3 },
];

console.log(products[1].price); // 6

Porque es un array, métodos de array funcionan en él, y es aquí donde ganan su valor. Extrae un campo de cada objeto con map, mantén registros coincidentes con filter, redúcelos a un valor con reduce:

js
const titles = products.map((product) => product.title);
console.log(titles); // ["Lámpara de escritorio", "Cuaderno", "Bolígrafo"]

const total = products.reduce((sum, product) => sum + product.price, 0);
console.log(total); // 49

El callback recibe un objeto por iteración, y lees sus campos por clave. Una vez que esto hace clic, "para cada registro, toma este campo" y "suma este campo en todos los registros" se convierten en una sola línea.

Un array de objetos es la forma canónica de datos estructurados, y métodos de array son cómo lo trabajas. Cada callback recibe un registro; lees sus campos por clave y transformas, filtras o agregas:

js
const orders = [
  { id: 1, total: 40, paid: true },
  { id: 2, total: 6, paid: false },
  { id: 3, total: 3, paid: true },
];

const paidTotal = orders
  .filter((order) => order.paid)
  .reduce((sum, order) => sum + order.total, 0);

console.log(paidTotal); // 43

Leer encadenando de arriba a abajo como un pipeline: mantén los pedidos pagados, luego suma sus totales. Esto se compone limpiamente porque cada paso toma un array de objetos y devuelve otro array (o, para reduce, un valor único).

Una cosa a tener en mente conforme crecen los datos: estos objetos se mantienen por referencia, que la sección avanzada abajo hace precisa. Mutar un objeto dentro de un callback map o forEach cambia el registro original en el array, no una copia, que es ocasionalmente lo que quieres y a menudo una sorpresa. Cuando intentas transformar sin tocar la fuente, construye objetos nuevos en lugar de editar los que te entregaron.

JunoObjetos y arrays juntos La mayoría de los datos reales es un array de objetos: una lista donde cada elemento es un registro con las mismas claves. Accede a uno con products[0], luego a un campo con products[0].title. Itera sobre el array para tocar cada registro, y tienes el patrón detrás de la mayoría del trabajo de datos que harás.
JunoObjetos y arrays juntos Un array de objetos es la forma en que llegan la mayoría de los datos reales, y los métodos de array están construidos para ello. map extrae un campo de cada registro, filter mantiene los coincidentes, reduce suma en ellos, con el callback recibiendo un objeto por turno. "Para cada registro, toma este campo" deja de ser un bucle y se convierte en una sola línea.
JunoObjetos y arrays juntos Los métodos de array se encadenan en un pipeline sobre registros: filtra hacia abajo, luego reduce a un valor, cada paso leyendo campos por clave. Observa que estos objetos se mantienen por referencia, así que mutar uno dentro de un callback map o forEach edita el original en su lugar. Cuando intentas transformar sin tocar la fuente, construye objetos nuevos en lugar de editar los que te entregaron.

A dónde van los objetos después

Los objetos y los arrays son las dos estructuras a las que recurrirás constantemente, y la mayoría de los datos que manejas son algún anidamiento de los dos: objetos con campos de array, arrays de objetos, y combinaciones de ambos. Empareja los con métodos de array y puedes dar forma a casi cualquier dato en la forma que necesites.

El siguiente paso es poner los objetos a trabajar en una página. Cuando llegas al DOM, los elementos que lees y cambias son también objetos, con claves que estableces y métodos que llamas, así que todo aquí se traslada directamente.