Protección de Propiedades Privadas en Prototipos JavaScript
Resumen
A partir del último código generado, crearemos un getter y setter a nuestra propiedad learningPaths dentro de nuestro prototipo Student para evitar que sea manipulado después de la creación de un estudiante.
Getters y Setters desde Object.defineProperty
Generamos un objeto private en el que colocaremos el atributo, _learningPaths el cual al principio será un array vacío. Previo a esto, borramos todo el código que viene después de la asignación de atributos en el objeto Student:
functionisObject(subject){returntypeof subject =="object";}functionisArray(subject){returnArray.isArray(subject);}functionrequiredParam(param){thrownewError(param +" es obligatorio");}functionLearningPath({// PROTOTIPO name =requiredParam("name"),// Campo es obligatorio courses =[],// Lista de Cursos que pertencen a la ruta de aprendizaje}){this.name= name;this.courses= courses;}functionStudent({// PROTOTIPO name =requiredParam("name"), email =requiredParam("email"), age, twitter, instagram, facebook, approvedCourses =[], learningPaths =[],}={}){// ASIGNACIÓN DE ATRIBUTOSthis.name= name;this.email= email;this.age= age;this.approvedCourses= approvedCourses;this.socialMedia={ twitter, instagram, facebook,};constprivate={// 👈👈"_learningPaths":[],};}
Con Object.defineProperty añadiremos el getter y setter respectivo a la propiedad learningPaths. En el setter es donde validaremos si un nuevo learning path que deseamos añadir es instancia del prototipo LearningPath:
functionisObject(subject){returntypeof subject =="object";}functionisArray(subject){returnArray.isArray(subject);}functionrequiredParam(param){thrownewError(param +" es obligatorio");}functionLearningPath({// PROTOTIPO name =requiredParam("name"),// Campo es obligatorio courses =[],// Lista de Cursos que pertencen a la ruta de aprendizaje}){this.name= name;this.courses= courses;}functionStudent({// PROTOTIPO name =requiredParam("name"), email =requiredParam("email"), age, twitter, instagram, facebook, approvedCourses =[], learningPaths =[],}={}){// ASIGNACIÓN DE ATRIBUTOSthis.name= name;this.email= email;this.age= age;this.approvedCourses= approvedCourses;this.socialMedia={ twitter, instagram, facebook,};constprivate={"_learningPaths":[],};// "this" referencia al prototipo "Student"Object.defineProperty(this,"learningPaths",{// 👈👈get(){// GETTERreturnprivate["_learningPaths"];},set(newLp){// SETTERif(newLp instanceofLearningPath){// Si es que SÍ es una instancia, añadimos al array privado "_learningPaths"private["_learningPaths"].push(newLp);}else{// "LPs" hace referencia a Learning Pathsconsole.warn("Alguno de los LPs NO es una instancia del prototipo LearningPath");}},});}
Con un bucle for in vamos a recorrer cada una de las rutas de aprendizaje que queramos asignarle al nuevo estudiante para invocar al setter que generamos. Este setter validará al learning path de turno si es en realidad una instancia del prototipo LearningPath:
functionisObject(subject){returntypeof subject =="object";}functionisArray(subject){returnArray.isArray(subject);}functionrequiredParam(param){thrownewError(param +" es obligatorio");}functionLearningPath({// PROTOTIPO name =requiredParam("name"),// Campo es obligatorio courses =[],// Lista de Cursos que pertencen a la ruta de aprendizaje}){this.name= name;this.courses= courses;}functionStudent({// PROTOTIPO name =requiredParam("name"), email =requiredParam("email"), age, twitter, instagram, facebook, approvedCourses =[], learningPaths =[],}={}){// ASIGNACIÓN DE ATRIBUTOSthis.name= name;this.email= email;this.age= age;this.approvedCourses= approvedCourses;this.socialMedia={ twitter, instagram, facebook,};constprivate={"_learningPaths":[],};// "this" referencia al prototipo "Student"Object.defineProperty(this,"learningPaths",{get(){// GETTERreturnprivate["_learningPaths"];},set(newLp){// SETTERif(newLp instanceofLearningPath){// Si es que SÍ es una instancia, añadimos al array privado "_learningPaths"private["_learningPaths"].push(newLp);}else{// "LPs" hace referencia a Learning Pathsconsole.warn("Alguno de los LPs que quieres añadir NO es una instancia del prototipo LearningPath");}},});for(learningPathIndex in learningPaths){// 👈👈// Al querer hacer una asignación, estamos invocando al setter de la// propiedad "learningPaths". Entonces, la ruta de aprendizaje ubicado// en el índice actual será validado por el setter para saber si es o no// instancia del prototipo LearningPath:this.learningPaths= learningPaths[learningPathIndex];}}
¡Listo! Nuestro atributo learningPaths quedó protegido. Intentemos crear un estudiante con sus respectivos learning paths. Luego intentemos añadir una ruta adicional que sea instancia del prototipo LearningPath y otra que no lo sea:
const escuelaWeb =newLearningPath({name:"Escuela de WebDev"});const escuelaData =newLearningPath({name:"Escuela de Data Science"});const juan =newStudent({email:"juanito@frijoles.co",name:"Juanito",learningPaths:[ escuelaWeb, escuelaData,],});console.log(juan.learningPaths);// ANTESconst escuelaMarketing =newLearningPath({name:"Escuela de Marketing"});juan.learningPaths={name:"Escuela Impostora"};// 👈👀juan.learningPaths= escuelaMarketing;console.log(juan.learningPaths);// DESPUES/* > Mensaje en consola
[
LearningPath { name: 'Escuela de WebDev', courses: [] },
LearningPath { name: 'Escuela de Data Science', courses: [] }
]
Alguno de los LPs NO es una instancia del prototipo LearningPath 👈👀
[
LearningPath { name: 'Escuela de WebDev', courses: [] },
LearningPath { name: 'Escuela de Data Science', courses: [] },
LearningPath { name: 'Escuela de Marketing', courses: [] }
]
*/
Para asignar los métodos y atributos privados en Clases (ahora en ES21) seran con un #
El propio JavaScript aplica la encapsulación de privacidad de estas características de clase.
classClassWithPrivateField{ #privateField;}
Ahora vamos a ver un Ejemplo
Metodos Privados
Vamos a crear una clase llamada People y vamos a tener varios metodos.
classPeople{showName(){console.log("My name is David")}showAge(){console.log("David is 21")}}
Para acceder a los metodos dentro de las clases, primero necesitamos instanciar la clase.
classPeople{showName(){console.log("My name is David")}showAge(){console.log("David is 21")}}const people =newPeople()people.showName()people.showAge()
Podemos ver My name is David y David is 21 en la consola.
Si queremos hacer showAge(), un método privado dentro de la clase People, por lo que fuera del alcance de la clase no es accesible.
Simplemente agregamos # a showAge() algo asi #showAge()
classPeople{showName(){console.log("My name is David")}#showAge(){console.log("David is 21")}}const people =newPeople()people.showName()people.showAge()
Podemos ver el resultado en nuestra consola. Un error es decir people.showAge que no es una función. Esto se debe a #showAge() que ahora es un método privado dentro de la clase People y solo se puede acceder a través de un método público dentro de la clase People.
Muy util , gracias .
Como debía ser desde el principio y no cosas raras, le tengo cariño a java pero ahora lo amo, jaja
Después de ver esta clase, creo que ya entiendo porqué la gente dice que JS es un lenguaje extraño 😅
si
si
jajajajajajaj estoy reeee perdido jajajajaja <3
El verdadero reto de esta clase fue entender la clase jeje.
.
Hice un diagrama de flujo que realmente no tiene nada de diagrama de flujo pero asi yo le pude entender un poquito mejor, espero les ayude a pasar este proceso de "entender js", una vez pasando el proceso de negación, la cosa se vuelve mas sencilla, keep going!!
.
Si, hace un par de clases como que están siendo mas confusas por falta de una dirección clara, al final no es muy difícil el tema, mas raro la POO en JS imposible pero no esta tan difícil tampoco
Llegados a este punto del curso creo que fue mala idea trabajarlo con Typescript :''v
Este clase ha sido muy enriquecedora con JS.
Por cierto, el reto queda algo así, mucho mas bonito con la sintaxis de class:
.
Claro, el chiste del curso completo es aprender cómo funciona Javascript internamente en relación al POO. TypeScript ya integra todo esto. Seguramente cuando se transpila el código TS a JS, el código final termina siendo muy similar al que vemos en este curso.
Son importantes estas bases para lograr una POO sólida en JS y seguramente TS se basa en todo esto.
Saludos!
Se parece a java xd
Y NOS PODEMOS AHORRAR 10 MINUTOS DE NUESTRAS VIDAS SI VEMOS EL VIDEO DESDE EL MINUTO 12 39
Con la sintaxis de clases use el prefijo # para indicar que una propiedad es privada.
classStudent{ #learningPaths =[]constructor({ name =requiredParam("name"), email =requiredParam("email"), age, twitter, facebook, approvedCourses =[], learningPaths =[],}={}){ name, email, age, twitter, facebook, approvedCourses
for(let index in learningPaths){if(learningPaths[index]instanceofLearningPath){this.#learningPaths.push(learningPaths[index])}}}setlearningPaths(newLp){if(newLp instanceofLearningPath){this.#learningPaths.push(newLp)}else{console.warn("Alguno de los LPs no es una instancia de la clase Learningpath")}}getlearningPaths(){returnthis.#learningPaths}#metodoPrivado(){console.log("uwu")}metodoPublico(){this.#metodoPrivado()}}
pero te sigue funcionando cuando tratas de hacer push propio? yo de ti le agregaria la consicional ademas para que si se pueda hacer push si esque se tiene algun permiso especial.... supongo, me estoy imaginando
En cuanto al push, tambien...
Si terminaste la clase ultra confundido porque dijo LearninnPaths hasta el cansancio, te explico la solución de forma limpia usando metodos de arrays.
Escondemos la propiedad
constprivate={_learningPaths:[]}
Modificamos el metodo .map con el que validabamos y empujabamos cada elemento que viniera dentro learningPaths, para que ahora haga uso de un setter que se utilice al llamar a la propiedad.
if(!isArray(learningPaths)){thrownewError('LP no es una lista valida')}else{ learningPaths.map(elm=>{this.learningPaths= elm
})}
Por último, declaramos una propiedad learningPath con el getter y setter para acceder a la propieda oculta, y dentro del setter hacemos la validación (que antes haciamos dentro del .map) para saber si es instancia del prototipo LearningPath
Object.defineProperty(this,"learningPaths",{get(){returnprivate._learningPaths},set(newLp){// debugger;if(newLp instanceofLearningPath){private._learningPaths.push(newLp)}else{console.warn(`${newLp} no es una LP valida`)}}})
Y listo, sin enredos, sin un monton de scroll, sin jergas adolescentes innecesarias, simplemente claro y consiso.
El codigo completo queda así
constprivate={_learningPaths:[]}Object.defineProperty(this,"learningPaths",{get(){returnprivate._learningPaths},set(newLp){// debugger;if(newLp instanceofLearningPath){private._learningPaths.push(newLp)}else{console.warn(`${newLp} no es una LP valida`)}}})if(!isArray(learningPaths)){thrownewError('LP no es una lista valida')}else{ learningPaths.map(elm=>{this.learningPaths= elm
})}
Saludos
Héroe !
Sos D10s! Capo!
Aquí mi solución usando la sintaxis de Clases y RORO! Ayúdame revisando mi código y diciéndome qué harías diferente =D
classStudent{constructor({ name =requiredParam("name"), email =requiredParam("email"), age, twitter, instagram, facebook, approvedCourses =[],}){this.name= name;this.email= email;this.age= age;this.approvedCourses= approvedCourses;this.socialMedia={ twitter, instagram, facebook,};const privado ={"_learningPaths":[],};Object.defineProperty(this,"learningPaths",{get(){return privado["_learningPaths"];},set(newLp){if(newLp instanceofLearningPath){ privado["_learningPaths"].push(newLp);}else{console.warn("Alguno de los LPs no es una instancia del prototipo LearningPath");}},})}}
Tuve problemas al declarar la constante 'private' dentro del constructor! Al parecer js aunque no suporte la palabra private ya la identifica como palabra reservada!
lo solucioné de la misma manera x'D, aún así creo que si desactivas el modo estricto debería dejarte usar la palabra "private", lo mismo con "public"
Hola @Eduardok, Cómo estás?. Si no malentiendo el código, creo que el problema con esta solución es que por cada instancia tendrás dos nuevos métodos get y set lo que sería poco eficiente. Esto se debe a que estos métodos están siendo declarados y asignados al objeto generado por el constructor, lo que causa que todas las instancias tendrán sus propios dos métodos gets y sets, lo que requerirá más espacio en memoria. Podría ser que lo más adecuado sería en lugar de agregar estos métodos get y set a la instancia, lo hiciera al prototipo, de esta manera serán los mismos dos métodos para todas las instancias, en lugar de dos métodos nuevos por instancia.
El for ... in que algunos reemplazamos con un for ... of, también se puede reemplazar con un forEarch:
functionisObject(subject){returntypeof subject =="object";}functionisArray(subject){returnArray.isArray(subject);}functionrequiredParam(param){thrownewError(param +" es un parametro obligatorio.");}classLearningPath{constructor({ name =requiredParam("name"), courses =[]}){this.name= name;this.courses= courses;}}classStudent{constructor({ name =requiredParam("name"), email =requiredParam("email"), age, twitter, instagram, facebook, approvedCourses =[], learningPaths =[],}){this.name= name;this.email= email;this.age= age;this.approvedCourses= approvedCourses;this._learningPaths=this.setterCall(learningPaths);this.socialMedia={ twitter, instagram, facebook,};}getlearningPaths(){returnthis._learningPaths;}setlearningPaths(newLearningPath){if(newLearningPath instanceofLearningPath){this._learningPaths.push(newLearningPath);}else{console.warn("Alguno de los LPs no es una instancia de los prototipos LearningPath");}}setterCall(learningPaths){this._learningPaths=[]; learningPaths.forEach((learningPath)=>{this.learningPaths= learningPath;});returnthis._learningPaths;}}const escuelaWeb =newLearningPath({name:"Escuela de WebDev"});const escuelaData =newLearningPath({name:"Escueal de Data Science"});const brandon =newStudent({email:"brandargel@gmail.co",name:"Brandon",learningPaths:[escuelaWeb, escuelaData,{name:"Escuela del Impostor"}],});
que clase tan enredada, repites mucho el nombre de la propiedad y se vuelve molesto :(
Para alguien que es novato en Programación Orientado en Objetos y es su primera vez que lo trabaja en JS, creo que le resultaría muy dificil entender que se está haciendo en la clase. Y es un poco tedioso y enredado que de repente, se te olvida que es lo que quieres hacer o para que lo quieres aplicar.
Me esta pasando justo eso a mi, primera vez que veo POO y hace poco empecé con JS, y ya mas de medio curso que vengo perdido 😥 mientras que el básico de POO lo entendí a la perfección, creo que para entender estos conceptos se necesitaba ejemplos mucho mas sencillos para entender como funcionan estos metodos prototipos y eso
< | | >
Hola comunidad, les dejo mi aporte relacionado con el reto.
Espero sea de utilidad.
functionisObject(subject){returntypeof subject =="object";}functionisArray(subject){returnArray.isArray(subject);}functionrequiredParam(param){thrownewError(param +" es un parametro obligatorio.");}classLearningPath{constructor({ name =requiredParam("name"), courses =[],}){this.name= name;this.courses= courses;}}classStudent{constructor({ name =requiredParam("name"), email =requiredParam("email"), age, twitter, instagram, facebook, approvedCourses =[], learningPaths =[],}){this.name= name;this.email= email;this.age= age;this.approvedCourses= approvedCourses;this._learningPaths=this.setterCall(learningPaths);this.socialMedia={ twitter, instagram, facebook,};}getlearningPaths(){returnthis._learningPaths;}setlearningPaths(newLP){if(newLP instanceofLearningPath){this._learningPaths.push(newLP);}else{console.warn("Alguno de los LPs no es una instancia de los prototipos LearningPath");}}setterCall(learningPaths){this._learningPaths=[];for(let index in learningPaths){this.learningPaths= learningPaths[index];}returnthis._learningPaths;}}const escuelaWeb =newLearningPath({name:"Escuela de WebDev"});const escuelaData =newLearningPath({name:"Escueal de Data Science"});const juan =newStudent({name:"Juanito",email:"juanito@frijoles.com",learningPaths:[ escuelaWeb, escuelaData,{name:"Escuela impostora",courses:[],},],});
la frase tipica del profe es 'si, pero, no. pero si'
En el minuto 11:04, en vez de eliminar la comprobación de que si learningPaths es un array (que definitivamente debería ser eliminada, ya que siempre va a ser cierta porque el valor por defecto es un array), debería remplazarse por la siguiente comprobación:
si learningPaths no esta vacío, pasa al for loop
Desde mi punto vista hace unas clases para enseñar algunos conceptos que son fáciles se están haciendo los videos muy largos y complejos sin necesidad.
Esto demuestra que la clase no está preparada en su totalidad.
Se pueden hacer ejemplos más simples.
Uf...lo que se sufría antes sin la sintaxis de clases. Ahora con # se soluciona todo 😅
Por si a alguien le sirve:
functionStudent({ name =requiredParam("name"), age, email =requiredParam("email"), twitter, instagram, facebook, approvedCourses =[], learningPaths =[],}={}){this.name= name;this.age= age;this.email= email;this.socialMedia={ twitter, instagram, facebook,};this.approvedCourses= approvedCourses;// Creando una constante privada, donde vamos a guardar la información que no queremos que puedan acceder a modificar el valor.constprivate={"_learningPaths":[],}// Creando un nuevo atributo dentro de this, asignandole un getter y un setter.Object.defineProperty(this,"learningPaths",{get(){returnprivate["_learningPaths"];},set(newLP){// Validación de que si cada uno de nuestros learningPaths son realmente una insntancia del prototipo LearningPath.if(newLP instanceofLearningPath){private["_learningPaths"].push(newLP);}else{console.warn("Alguno de los LPs no es una instancia del prototipo LP");}},configurable:false,// Evitamos que se pueda cambiar get y set});// Ciclo for para que cada uno de los learningPaths que enviemos desde un principio cuando estamos instanciando nuestro estudiante, por cada uno de ello vamos a llamar al setter, para verificar y asignarle en caso de que la validación sea correcta que ese nuevo learningPath debe de estar dentro de la propiedad learningPaths que es privada.for(let learningPathIndex in learningPaths){this.learningPaths= learningPaths[learningPathIndex];};};