La clase `Object` en JavaScript
Este artículo explica la clase Object en JavaScript.
Este artículo explica la clase Object en JavaScript, incluyendo ejemplos prácticos.
YouTube Video
La clase Object en JavaScript
Object es un objeto incorporado que sirve como base para todos los objetos en JavaScript. Muchas de las características principales del lenguaje, como la gestión de propiedades, la herencia (cadena de prototipos), la enumeración, la clonación y el congelamiento, se proporcionan a través del comportamiento de Object.
Object.create
Hay varias formas de crear objetos, y debes usarlas adecuadamente dependiendo de tu propósito.
Literal de objeto (la más común)
El siguiente código muestra la forma más simple y legible de crear un objeto.
1// Create an object using object literal
2const user = {
3 name: "Alice",
4 age: 30,
5 greet() {
6 return `Hello, I'm ${this.name}`;
7 }
8};
9
10console.log(user.greet()); // "Hello, I'm Alice"
- En este ejemplo, las propiedades y los métodos se definen usando literales. Es sencillo y generalmente ofrece un rendimiento superior.
Constructor new Object()
El constructor Object se usa raramente, pero es útil comprender su comportamiento.
1// Create an object using the Object constructor
2const objFromCtor = new Object();
3objFromCtor.x = 10;
4objFromCtor.y = 20;
5
6console.log(objFromCtor); // { x: 10, y: 20 }
new Object()devuelve un objeto vacío, pero el literal{}es más corto y más común.
Especificar el prototipo con Object.create
Object.create se utiliza para crear un objeto con un prototipo especificado.
1// Create an object with a specified prototype
2const proto = { hello() { return "hi"; } };
3const obj = Object.create(proto);
4obj.name = "Bob";
5
6console.log(obj.hello()); // "hi"
7console.log(Object.getPrototypeOf(obj) === proto); // true
Object.createes ideal para el diseño de objetos basado en herencia, permitiendo controlar precisamente la cadena de prototipos.
Atributos de propiedades y descriptores
Las propiedades tienen atributos como 'value', 'writable', 'enumerable', y 'configurable', los cuales se pueden controlar en detalle con Object.defineProperty.
Ejemplo básico de uso de defineProperty
A continuación, un ejemplo de cómo definir propiedades no enumerables y de solo lectura utilizando defineProperty.
1// Define a non-enumerable read-only property
2const person = { name: "Carol" };
3
4Object.defineProperty(person, "id", {
5 value: 12345,
6 writable: false,
7 enumerable: false,
8 configurable: false
9});
10
11console.log(person.id); // 12345
12console.log(Object.keys(person)); // ["name"] — "id" is non-enumerable
13person.id = 999; // silently fails or throws in strict mode
14console.log(person.id); // still 12345
- Usar
definePropertypermite controlar con precisión el comportamiento de las propiedades, como la enumeración, la sobrescritura y la eliminación.
Propiedades accesoras (getter / setter)
Con los accesores, puedes inyectar lógica en la lectura y escritura de propiedades.
1// Use getter and setter to manage internal state
2const data = {
3 _value: 1,
4 get value() {
5 return this._value;
6 },
7 set value(v) {
8 if (typeof v === "number" && v > 0) {
9 this._value = v;
10 } else {
11 throw new Error("value must be a positive number");
12 }
13 }
14};
15
16console.log(data.value); // 1
17data.value = 5;
18console.log(data.value); // 5
19// data.value = -1; // would throw
- Con
getterysetter, puedes tratar el acceso a propiedades como una API externa y añadir validaciones o efectos secundarios.
Prototipo y herencia (prototype / __proto__ / Object.getPrototypeOf)
La herencia en JavaScript se basa en la cadena de prototipos, no en las clases. Los objetos pueden referirse a otros objetos como sus prototipos.
Object.getPrototypeOf y Object.setPrototypeOf
El siguiente ejemplo muestra cómo inspeccionar y establecer prototipos.
1// Inspect and change prototype
2const base = { speak() { return "base"; } };
3const derived = Object.create(base);
4console.log(Object.getPrototypeOf(derived) === base); // true
5
6const other = { speak() { return "other"; } };
7Object.setPrototypeOf(derived, other);
8console.log(derived.speak()); // "other"
Object.getPrototypeOfrecupera el prototipo de un objeto.Object.setPrototypeOfcambia el prototipo de un objeto existente, pero debes usarlo cuidadosamente ya que puede afectar el rendimiento.
Métodos incorporados importantes.
Explicaremos claramente los métodos más utilizados y relevantes, seleccionados de los métodos de instancia proporcionados por Object.prototype, así como los métodos estáticos que posee Object.
hasOwnProperty, isPrototypeOf, toString, valueOf
hasOwnProperty, isPrototypeOf, toString y valueOf definen el comportamiento básico de los objetos.
1// Demonstrate prototype methods
2const base = { greet() { return "hello"; } };
3const child = Object.create(base);
4const a = { x: 1 };
5
6console.log(a.hasOwnProperty("x")); // true
7console.log(a.hasOwnProperty("toString")); // false — toString is inherited
8
9console.log(a.toString()); // "[object Object]" by default
10
11console.log(base.isPrototypeOf(child)); // true
12console.log(Object.prototype.isPrototypeOf(child)); // true
hasOwnPropertyes un método esencial para comprobar si una propiedad está directamente en el objeto.isPrototypeOfverifica si el objeto de destino lo tiene a sí mismo como prototipo.
Object.keys, Object.values, Object.entries
Object.keys, Object.values y Object.entries devuelven listas de las propiedades enumerables propias de un objeto. Son útiles para la iteración y la transformación.
1// Keys, values and entries
2const item = { id: 1, name: "Widget", price: 9.99 };
3
4// ["id", "name", "price"]
5console.log(Object.keys(item));
6
7// [1, "Widget", 9.99]
8console.log(Object.values(item));
9
10// [["id",1], ["name","Widget"], ["price",9.99]]
11console.log(Object.entries(item));- Estos se usan frecuentemente para iterar y transformar objetos.
Object.assign
Object.assign se utiliza para copiar superficialmente y para fusionar objetos. Ten en cuenta que los prototipos y las propiedades accesoras no se copian.
1// Shallow copy / merge using Object.assign
2const target = { a: 1 };
3const source = { b: 2 };
4const result = Object.assign(target, source);
5
6console.log(result); // { a: 1, b: 2 }
7console.log(target === result); // true (merged into target)
- En objetos anidados, solo se copian las referencias, así que necesitas una implementación diferente para la clonación profunda.
Object.freeze, Object.seal, Object.preventExtensions
Object.freeze, Object.seal y Object.preventExtensions controlan la mutabilidad de los objetos.
1// Freeze vs seal vs preventExtensions
2const obj = { a: 1 };
3Object.freeze(obj);
4obj.a = 2; // fails silently or throws in strict mode
5delete obj.a; // fails
6
7const obj2 = { b: 2 };
8Object.seal(obj2);
9obj2.b = 3; // allowed
10// delete obj2.b; // fails
11
12const obj3 = { c: 3 };
13Object.preventExtensions(obj3);
14obj3.d = 4; // fails
freezees el más estricto; previene cualquier cambio en las propiedades del objeto.sealimpide agregar o eliminar propiedades, pero permite cambiar los valores de las propiedades existentes.preventExtensionssolo evita la adición de nuevas propiedades; las propiedades existentes aún se pueden cambiar o eliminar.
Enumerabilidad de objetos, orden y for...in / for...of
for...in enumera los nombres de las propiedades enumerables, pero también incluye propiedades en la cadena de prototipos, por lo que a menudo se usa en combinación con hasOwnProperty. Combinar Object.keys() con for...of es más seguro y aclara tus intenciones.
1// Safe enumeration
2const obj = Object.create({ inherited: true });
3obj.own = 1;
4
5for (const key in obj) {
6 if (obj.hasOwnProperty(key)) {
7 console.log("own prop:", key);
8 } else {
9 console.log("inherited prop:", key);
10 }
11}
12
13for (const key of Object.keys(obj)) {
14 console.log("key via Object.keys:", key);
15}- Las reglas para la enumeración de propiedades están definidas en la especificación ECMAScript, y hay casos donde el orden de enumeración está garantizado y otros donde no lo está. Como regla general, las claves interpretadas como números se ordenan en orden ascendente, mientras que las demás claves siguen el orden de inserción.
Clonación y copiado profundo
Hay dos tipos de copiado de objetos: copiado superficial usando Object.assign o la sintaxis de propagación, y copiado profundo usando métodos recursivos. Es importante usarlos apropiadamente según la situación.
Copiado superficial (sintaxis de propagación / Object.assign)
1// Shallow copy with spread operator
2const original = { a: 1, nested: { x: 10 } };
3const shallow = { ...original };
4shallow.nested.x = 99;
5console.log(original.nested.x); // 99 — nested object is shared
- Con el copiado superficial, los objetos anidados comparten referencias, por lo que los cambios en el objeto original pueden afectar la copia.
Copia profunda simple (con advertencias)
Usar el truco de JSON es una forma rápida de realizar una copia profunda, pero tiene desventajas como la pérdida de funciones, objetos Date, referencias circulares y valores undefined. Para una clonación profunda real, necesitas usar una biblioteca dedicada.
1// Deep clone using JSON methods — limited use-cases only
2const source = { a: 1, d: new Date(), nested: { x: 2 } };
3const cloned = JSON.parse(JSON.stringify(source));
4console.log(cloned); // ok for plain data, but Date becomes string, functions lost
- Los métodos basados en JSON son convenientes para manejar rápidamente datos simples, pero en casos comunes su comportamiento puede fallar.
Mixins y composición de objetos
En lugar de herencia múltiple, a menudo se usa el patrón de componer comportamientos mediante mixins.
1// Simple mixin function
2const canEat = {
3 eat() { return `${this.name} eats`; }
4};
5const canWalk = {
6 walk() { return `${this.name} walks`; }
7};
8
9function createPerson(name) {
10 const person = { name };
11 return Object.assign(person, canEat, canWalk);
12}
13
14const p = createPerson("Dana");
15console.log(p.eat()); // "Dana eats"
16console.log(p.walk()); // "Dana walks"
- Los mixins son flexibles, pero debes tener cuidado con las colisiones de nombres de propiedades y la capacidad de prueba.
Errores comunes y buenas prácticas
Aquí hay algunos errores comunes y buenas prácticas.
-
Mutabilidad Los objetos son mutables por defecto. En aplicaciones con gestión de estado, considera estructuras de datos inmutables usando
Object.freezeo una biblioteca inmutable. -
Contaminación del prototipo Fusionar datos externos en un objeto directamente con
Object.assigno bucles puede causar efectos secundarios inesperados con propiedades especiales como__proto__oconstructor, creando riesgos de seguridad. Filtra la entrada del usuario antes de fusionarla directamente. -
Peligros de
for...infor...intambién enumera propiedades del prototipo, así que verifica conhasOwnProperty. UsarObject.keyses más claro. -
Mal uso del copiado superficial Considera si necesitas copiado profundo para evitar que los cambios en objetos anidados afecten al objeto original.
Ejemplo Práctico: Patrón de Actualización de Objeto Inmutable
Los patrones que devuelven un nuevo objeto sin modificar directamente el estado se usan comúnmente en React y bibliotecas similares.
1// Immutable update example
2const state = { todos: [{ id: 1, text: "Buy milk", done: false }] };
3
4// Toggle todo done immutably
5function toggleTodo(state, todoId) {
6 return {
7 ...state,
8 todos: state.todos.map(t => t.id === todoId ? { ...t, done: !t.done } : t)
9 };
10}
11
12const newState = toggleTodo(state, 1);
13console.log(state.todos[0].done); // false
14console.log(newState.todos[0].done); // true
- Este código es un ejemplo de cómo crear un nuevo objeto de estado sin modificar directamente el objeto
stateoriginal. La funcióntoggleTodocopia el arreglotodosy devuelve un nuevo objeto con solo el elemento objetivo modificado, de modo que el objetostateoriginal permanece sin cambios. - Las actualizaciones inmutables reducen los efectos secundarios y facilitan la gestión del estado.
Ejemplo Práctico: Fusión Segura (Cuidado con la Contaminación de Prototipo)
Al fusionar JSON externo, ignora __proto__ para evitar la contaminación del prototipo.
1// Safe merge ignoring __proto__ keys
2function safeMerge(target, source) {
3 for (const key of Object.keys(source)) {
4 if (key === "__proto__" || key === "constructor") continue;
5 target[key] = source[key];
6 }
7 return target;
8}
9
10const target = {};
11const source = JSON.parse('{"a":1,"__proto__":{"polluted":true}}');
12safeMerge(target, source);
13console.log(target.polluted); // undefined — safe
14console.log({}.polluted); // undefined — prototype not polluted
- Este tipo de protección también es importante en bibliotecas y frameworks.
Consideraciones de rendimiento
Con respecto al rendimiento, considera los siguientes puntos:.
- Evita cambios frecuentes de prototipo (
Object.setPrototypeOf) o adición/eliminación dinámica de propiedades, ya que esto dificulta la optimización del motor. - Al generar muchos objetos pequeños, la optimización se vuelve más efectiva si usas objetos con una estructura uniforme (el mismo conjunto de propiedades).
- La clonación profunda es costosa. Minimiza su uso o considera utilizar actualizaciones basadas en diferencias.
Resumen
Object es fundamental en JavaScript, proporcionando una variedad de características como creación de objetos, control de propiedades, herencia, copiado y gestión de la mutabilidad. Es importante comprender APIs como Object.defineProperty, Object.assign y Object.freeze, y diseñar con cuidado para evitar problemas como la contaminación del prototipo y la copia superficial.
Puedes seguir el artículo anterior utilizando Visual Studio Code en nuestro canal de YouTube. Por favor, también revisa nuestro canal de YouTube.