Definición y Control de Propiedades en Objetos JavaScript

Clase 4 de 20Curso Intermedio de Programación Orientada a Objetos en JavaScript

Resumen

El método defineProperty de la superclase Object no solo nos permite definir nuevos atributos en un objeto, sino también configurar las siguientes propiedades:

  • Configurable: indica si el nuevo atributo puede ser eliminado.
  • Enumerable: indica si el nuevo atributo podrá ser mostrado mediante funciones que listen las propiedades de un objeto. Hay excepciones en las que igual puede ser visualizado un atributo que tenga definido como false la propiedad enumerable.
  • Writable: indica si el nuevo atributo puede ser modificado de valor.

Normalmente, estas propiedades por defecto son definidas como true por JavaScript, sin embargo, si generamos los atributos de un objeto con Object.defineProperty, podríamos definirlas a nuestro gusto.

const juan = {
  name: "Juanito",
  age: 18,
  approvedCourses: ["Curso 1"],
  addCourse(newCourse) {
    console.log("This", this);
    console.log("This.approvedCourses", this.approvedCourses);
    this.approvedCourses.push(newCourse);
  }
};

Object.defineProperty(juan, "nombreNuevaPropiedad", {
	value: "JavaScript", // Valor que tendrá
	enumerable: false,
	writable: true,
	configurable: false,
});

Accesibilidad a los atributos de un objeto

Con configurable, enumerable y writable podemos limitar el acceso y modificación de los atributos de un objeto. Veamos su funcionamiento mediante un par de ejemplos:

Atributos que no puedan ser listados

Definimos enumerable como false. Este atributo recién creado no se podrá visualizar si por ejemplo intentamos listar las llaves del objeto usando Object.keys:

// Definimos el objeto
const juan = {
  name: "Juanito",
  age: 18,
  approvedCourses: ["Curso 1"],
  addCourse(newCourse) {
    console.log("This", this);
    console.log("This.approvedCourses", this.approvedCourses);
    this.approvedCourses.push(newCourse);
  }
};

Object.defineProperty(juan, "navigator", { // Creamos un nuevo atributo
	value: "Chrome",
	enumerable: false,
	writable: true,
	configurable: true,
});

console.log( // Imprimimos las llaves del objeto
	Object.keys(juan)
); // [ 'name', 'age', 'approvedCourses', 'addCourse' ]

Sin embargo, hay una excepción si usamos Object.getOwnPropertyNames:

// Definimos el objeto
const juan = {
  name: "Juanito",
  age: 18,
  approvedCourses: ["Curso 1"],
  addCourse(newCourse) {
    console.log("This", this);
    console.log("This.approvedCourses", this.approvedCourses);
    this.approvedCourses.push(newCourse);
  }
};

Object.defineProperty(juan, "navigator", { // Creamos un nuevo atributo
	value: "Chrome",
	enumerable: false, // 👀
	writable: true,
	configurable: true,
});

console.log( // Imprimimos las propiedades del objeto
	Object.getOwnPropertyNames(juan)
); // [ 'name', 'age', 'approvedCourses', 'addCourse', 'navigator' ] 👈 Ya nos aparece

Atributos que no se puedan eliminar

Para ello definimos configurable como false en la nueva propiedad:

// Definimos el objeto
const juan = {
  name: "Juanito",
  age: 18,
  approvedCourses: ["Curso 1"],
  addCourse(newCourse) {
    console.log("This", this);
    console.log("This.approvedCourses", this.approvedCourses);
    this.approvedCourses.push(newCourse);
  }
};

Object.defineProperty(juan, "terminal", { // Creamos un nuevo atributo
	value: "WSL",
	enumerable: true,
	writable: true,
	configurable: false, // 👀
});

console.log( // Mostramos las propiedades del objeto previamente... 👁👁
	Object.keys(juan)
); // [ 'name', 'age', 'approvedCourses', 'addCourse', 'terminal' ]

delete terminal; // Intentamos eliminar ❌

console.log( // Listamos los atributos para comprobar si se eliminó `terminal` 🤔
	Object.keys(juan)
); // [ 'name', 'age', 'approvedCourses', 'addCourse', 'terminal' ] 👈 NO se eliminó

Atributos que no se puedan sobreescribir

Definimos writable como false:

// Definimos el objeto
const juan = {
  name: "Juanito",
  age: 18,
  approvedCourses: ["Curso 1"],
  addCourse(newCourse) {
    console.log("This", this);
    console.log("This.approvedCourses", this.approvedCourses);
    this.approvedCourses.push(newCourse);
  }
};

Object.defineProperty(juan, "editor", { // Creamos un nuevo atributo
	value: "VSCode",
	enumerable: true,
	writable: false,
	configurable: true,
});

console.log(juan.editor); // "VSCode"

juan.editor = "Atom"; // Intentamos sobreescribirlo

console.log(juan.editor); // "VSCode" 👈 No cambió

Qué es Object.seal y Object.freeze

El método seal “sella” un determinado objeto. Es decir:

  • Impide que nuevas propiedades sean agregadas.
  • Define como configurable: false todos los atributos del objeto, con lo que impide que sean borradas.
  • Los atributos sí pueden ser modificados, ya que la propiedad writable permanece asignado como true.
// Definimos el objeto
const juan = {
  name: "Juanito",
  age: 18,
  approvedCourses: ["Curso 1"],
  addCourse(newCourse) {
    console.log("This", this);
    console.log("This.approvedCourses", this.approvedCourses);
    this.approvedCourses.push(newCourse);
  }
};

Object.seal(juan); // "Sellamos" el objeto

// Listamos para saber las llaves actuales:
console.log(Object.keys(juan)); // [ 'name', 'age', 'approvedCourses', 'addCourse' ]

delete age; // Intentamos eliminar un atributo del objeto

// Listamos para observar si hubo cambios:
console.log(Object.keys(juan)); // [ 'name', 'age', 'approvedCourses', 'addCourse' ]

El método freeze “congela” un objeto. Es decir:

  • Impide que se le agreguen nuevas propiedades.
  • Impide que sean eliminadas propiedades ya existentes.
  • Evita que sus propiedades writable, enumerable y configurable sean modificadas.
// Definimos el objeto
const juan = {
  name: "Juanito",
  age: 18,
  approvedCourses: ["Curso 1"],
  addCourse(newCourse) {
    console.log("This", this);
    console.log("This.approvedCourses", this.approvedCourses);
    this.approvedCourses.push(newCourse);
  }
};

Object.freeze(juan); // "Congelamos" el objeto

// Listamos para saber las llaves actuales:
console.log(Object.keys(juan)); // [ 'name', 'age', 'approvedCourses', 'addCourse' ]

delete approvedCourses; // Intentamos eliminar un atributo del objeto
juan.name = "Carlitos"; // Intentamos sobreescribir el valor de este atributo

// Listamos para observar si hubo cambios:
console.log(Object.keys(juan)); // [ 'name', 'age', 'approvedCourses', 'addCourse' ]
// Verificamos si cambió el valor de `name`:
console.log(juan.name); // "Juanito"

Conozcamos ahora cómo funciona la memoria en JavaScript. 🤔👨‍💻

Contribución creada por: Martín Álvarez (Platzi Contributor) con el aporte de Zajith Corro Viveros.