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 objetoconst 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 atributovalue:"Chrome",enumerable:false,writable:true,configurable:true,});console.log(// Imprimimos las llaves del objetoObject.keys(juan));// [ 'name', 'age', 'approvedCourses', 'addCourse' ]
Sin embargo, hay una excepción si usamos Object.getOwnPropertyNames:
// Definimos el objetoconst 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 atributovalue:"Chrome",enumerable:false,// 👀writable:true,configurable:true,});console.log(// Imprimimos las propiedades del objetoObject.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 objetoconst 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 atributovalue:"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 objetoconst 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 atributovalue:"VSCode",enumerable:true,writable:false,configurable:true,});console.log(juan.editor);// "VSCode"juan.editor="Atom";// Intentamos sobreescribirloconsole.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 objetoconst 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 objetoconst 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 objetojuan.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"