👉 Ver todas las notas
- Objetos
- POO
- Prototype
- Las funciones son funciones... y objetos
- Factory Function vs
constructor isPrototypeOf,getPrototypeOfeinstanceof- Creación de objetos
bind- El problema que tenemos al usar
this Class- Polimorfismo
- Getters & Setters
- Métodos estáticos
- POO: Conceptos fundamentales explicados brevemente
- 🎆 Bonus
- Para seguir aprendiendo...
- 📚 Libros recomendados sobre OOP en JS
- Conclusión
// 1constballXPosition=20;constballYPosition=40;constballColor='red';constballSize=2;// 2constball={xPosition: 20,yPosition: 40,color: 'red',size: 2};// 3constball={position: {x: 20,y: 40},color: 'red',size: 2};- Usando notación de ES6 para los métodos, se puede simplificar un poco
constuserOne={email: "ryu@ninjas.com",name: "Ryu",login(){console.log(`${this.email} has logged in!`)}};- Si tenemos una serie de variables/constantes y funciones relacionadas, podemos pensar que quizás nos convenga agruparlas, combinarlas de alguna forma, en una unidad.
- Podemos agruparlas en una unidad que conocemos como objeto
- Vamos a llamar propiedades a estas variables/constantes y métodos a las funciones
⭐ un objeto es una colección de datos/funcionalidades relacionados (variables y funciones), que llamamos propiedades y métodos cuando se encuentran dentro de un objeto
- Un objeto es una entidad que tiene propiedades
- Si una de estas propiedades define comportamiento o funcionalidad, la conocemos como método
constarray=[1,2,3];array.length// length propertyarray.map(x=>z**2);// map method- Las propiedades definen características propias de un objeto
- Los métodos definen comportamientos propios de un objeto
- Para acceder a una propiedad o método de un objeto, podemos utilizar:
- dot notation:
object.propertyName,object.methodName()- Debe ser el nombre literal de la propiedad
- bracket notation:
object['propertyName'],object['methodName']- La expresión entre
[]se evalúa para obtener el nombre de la propiedad. La utilizamos cuando la propiedad es dinámica, es decir, puede variar. Ejemplo, cuando iteramos con unfor.. in
- La expresión entre
- Para crear propiedades que no tengan un nombre válido en JS, usamos strings. Ej:
'full name': Homero J. Simpson
- dot notation:
- Para chequear si un objeto tiene una propiedad determinada, usamos el método
hasOwnProperty()(todos los objetos lo tienen)- También podemos usar el operador
in, que recibe como string el nombre de la propiedad y retornatruesi esta existe en el objeto. Ej:console.log('length' in [])
- También podemos usar el operador
constobj={};// empty objectconstmyCar={make: "Ford",model: "Mustang"}constobj=newObject();// empty object constmyCar=newObject();myCar.make="Ford";myCar.model="Mustang";- Son equivalentes
- Programación Orientada a Objetos es un paradigma de programación que utiliza objetos para modelar entidades del mundo real
- Los objetos son el centro del Paradigma Orientado a Objetos, mejor conocido como POO (OOP en inglés)
- JavaScript no sigue el paradigma más 'tradicional' de objetos, basado en clases, sino el basado en prototipos, aka objetos sin clases
⭐ Un paradigma de programación es cualquier enfoque sistemático que tomamos para intentar controlar la complejidad de nuestro código, haciendo que sea más fácil de entender y razonar (brinda estructura), mantener, modificar y extender (agregar features, funcionalidad)
constaTeletubbie={name: 'Po',color: '#ff0000',currentPosition: 0,goForward: function(){this.currentPosition+=1;},goForwardUsingScooter: function(){this.currentPosition+=2;},goBack: function(){this.currentPosition-=1;}}aTeletubbie.name;aTeletubbie.color;aTeletubbie.currentPosition;aTeletubbie.goForward();aTeletubbie.currentPosition;- Un paradigma de programación es un marco conceptual (framework mental), un conjunto de ideas que describe y setea una forma de entender cómo construimos y desarrollamos software.
⭐ Tener nociones de estos paradigmas nos va a ayudar a entender mejor las herramientas que utilizamos
⚠️ Problema: las propiedades pueden tomar valores únicos, pero en el caso de los métodos, estamos repitiendo las mismas funciones una y otra vez, para cada objeto (y rompiendo el principio DRY).Solución: mover los métodos a otro objeto (único) y que el intérprete, en el caso de que no los encuentre en los objetos anteriores, los busque en este otro
constcat={sound: 'meow!'}cat.talk();constcat={sound: 'meow!'}constanimal={talk(){console.log(this.sound);}}Object.setPrototypeOf(cat,animal);cat.talk();⭐ En JS, utilizamos prototipos para delegar características (propiedades) y comportamiento (métodos) a otros objetos
- Las propiedades propias de un objeto (es decir, las que están definidas en él) tienen precedencia sobre las propiedades de su prototipo que tengan el mismo nombre
- El prototipo de un objeto actúa como fallback: si JS no encuentra una propiedad en un objeto, va a buscarla a su prototipo y sino al prototipo del prototipo, etc
- Esto se conoce como Prototype Chain
- Esta cadena termina con el prototipo de
Object,Object.prototype, que esnull, porquenullno es un objeto y por lo tanto no puede tener una propiedad__proto__
- Podemos utilizar el método
hasOwnProperty()para diferenciar entre las propiedades propias de un objeto de las propiedades que hereda de su prototipo (inen cambio nos dice si una propiedad pertenece a la cadena de prototipos de un objeto) - Podemos aumentar o extender el prototipo de una función constructora (ó Factory Function) ya existente modificando su propiedad
prototypey todos los objetos creados con esta función tendrán las nuevas propiedades
functionDog(){}Dog.prototype.breed='Bulldog';constmyDog=newDog();myDog.breedmyDog.__proto__// prototype sólo existe en las funcionesmyDog.prototypeDog.prototypefunctionGiraffe(){}Giraffe.prototypeconstkoala={};// prototype es una propiedad que contiene un objetokoala.prototypekoala.__proto__// __proto__ es una referencia, no un objetokoala.__proto__===Object.prototypeEjecutar el siguiente código en la consola y analizar qué pasa y por qué
// caso 1
const arr = [];
arr.__proto__ = null;
arr.push(1);
// caso 2
const arr = [];
arr.__proto__.push = function() {
console.log('Nope 😈');
}
arr.push(1);
arr.push('a');
// caso 3
const arr = [];
arr.__proto__ = {}
arr.push(1);
// caso 4
const arr = [];
delete arr.__proto__.push;
arr.push(1);
- Recordemos que las funciones son First-Class citizens
- Por lo tanto podemos tratarlas como cualquier otro valor, por ejemplo pasarlas por parámetro o retornarlas desde otra función
- Por eso también decimos que las funciones en javascript son funciones de alto orden
- Y todo esto lo podemos hacer porque las funciones... son objetos!
// creando funciones con la función constructoraconstsum=newFunction('a','b','return a + b');console.log(sum(2,6));- Todas las funciones en JavaScript tienen por default una propiedad llamada
prototypeen su 'versión objeto'- Esta propiedad contiene incialmente un objeto "vacío", sin propiedades propias
- El valor de
prototypees un objeto- Podemos agregarle propiedades y métodos, es decir extenderlo; incluso reemplazarlo por otro objeto que decidamos
- Este objeto es el que vamos a utilizar como prototipo, en el caso de que utilicemos esta función para construir nuevos objetos (estas funciones se conocen como Factory Functions)
- Por lo tanto, los nuevos objetos que creemos utilizando esta función, tendrán como prototipo al definido en la propiedad
prototypede la función- Esto se logra seteando en el nuevo objeto una propiedad oculta,
__proto__que es una referencia (no una copia!) a esta propiedadprototype, es decir, a su prototipo
- Esto se logra seteando en el nuevo objeto una propiedad oculta,
functionmultiplyBy2(num){returnnum*2;}multiplyBy2.stored=5;multiplyBy2(3);// 6multiplyBy2.stored;// 5multiplyBy2.prototype;// {}- En JavaScript, cualquier función puede retornar un objeto. Cuando no se trata de una función constructora o clase, la llamamos Factory Function
functionPerson(firstName,lastName,age){constperson=Object.create();// usamos `person`en lugar de `this` porque en este caso `this` no refiere al objeto nuevo que creamos, sino al globalperson.firstName=firstName;person.lastName=lastName;person.age=age;returnperson;}constperson=Person('Dare','Devil',32);- Por convención, se utiliza siempre la primer letra del nombre de la función en mayúscula para indicar que es una función constructora
- Se invocan utilizando la keyword
new - No necesitamos crear ni devolver el nuevo objeto a mano,
newya se encarga de eso ⚠️ No podemos utilizar arrow functions como funciones constructoras ya que elthisque utilizan no puede hacer referencia a un nuevo objeto creado
functionPerson(firstName,lastName,age){this.firstName=firstName;this.lastName=lastName;this.age=age;}constperson=newPerson('Dare','Devil',32);functionProfessor(name,teaching,subjects){this.name=name;this.isTeaching=teaching;this.subjects=subjects;}Professor.prototype={showSubjects(){console.log(this.subjects);}}constprofessorX=newProfessor('Charles Xavier',true,['telepathy','leadership']);console.log(Professor.prototype.isPrototypeOf(professorX));console.log(Object.getPrototypeOf(professorX));console.log(professorXinstanceofProfessor);isPrototypeOf: sirve para chequear si un objeto es prototipo de otro ó es la clase que se usó para crearlogetPrototypeOf: retorna el prototipo (objeto) de un objetoinstanceof: sirve para chequear si un objeto fue creado a partir de una determinada función constructora o clase; un objeto creado por un constructor es una instancia de ese constructor
- Es un método de
Objectque crea un nuevo objeto, con el prototipo seteado a un cierto objeto - Es más natural para el modelo de prototipos que la keyword
new - Utilizar
Object.createen lugar deObject.setPrototypeOf
constanimal={init(sound){this._sound=sound;returnthis;},talk(){console.log(this._sound);}}constcat=Object.create(animal).init('meow!');cat.talk();- Crea un nuevo objeto vacío, el cual asigna a
this - Llama a la función constructora ó Factory Function
- Si llamamos a la función que construye el nuevo objeto (Factory Function) sin
newadelante (podemos, porque es una función),thisserá una referencia al objeto globalwindowy no funcionará como esperamos. Por eso a modo de indicación, se suele escribir la primer letra de estas funciones con mayúscula, para indicar que se debe invocar usandonew
- A este nuevo objeto le setea una propiedad oculta,
__proto__, la cual tendrá una referencia a la propiedadprototipe(objeto) de la función - Si la Factory Function recibe algún parámetro, los usa para setear propiedades de este nuevo objeto
- Retorna el nuevo objeto
functionUserCreator(name,score){// creamos un objeto vacío y lo enlazamos con su prototipo seteando su popiedad oculta __proto__constnewUser=Object.create(userFunctions);// seteamos sus propiedadesnewUser.name=name;newUser.score=score;returnnewUser;}// `prototype`: el objeto nuevo va a heredar estas propiedadesconstuserFunctions={increment(){this.score++;},login(){console.log('You have logged in');}}// creamos un nuevo usuarioconstuser=UserCreator('Sarah Connor',7);// interactuamos con el usuario a través de sus métodosuser.login();newautomatiza todo este proceso
Object.createcrea un nuevo objeto vacío y además le asigna a este el prototipo que nosotros querramos, si le pasamos un argumento, sino le asignaObjectcomo prototiponewen cambio, es una llamada a una función constructora (ó Factory Function), la cual también puede recibir argumentos, pero en este caso son para setear otras propiedades del objeto y no su prototipo- En este caso, el prototipo del nuevo objeto se obtiene a partir de la propiedad
prototipe(objeto) de la función, a la cual se setea una referencia en la propiedad__proto__del nuevo objeto
- En este caso, el prototipo del nuevo objeto se obtiene a partir de la propiedad
- Por último, con
Object.createpodemos crear un objeto que no herede de nadie (no tenga prototipo), usandoObject.create(null); mientras que, si seteamosSomeConstructor.prototype = null, el nuevo objeto va a heredar deObject.prototype
constx={prop1: ...,prop2: ...,
...
};constobject=Object.create()// prototipo de object : ObjectconstanotherObject=Object.create(x)// prototipo de anotherObject: xconstnewObject=newSomeConstructor();// => prototipo de newObject: SomeConstructor.prototypeconstkittie={_sound: 'MEOW',talk(){console.log(this._sound);}}kittie.talk();consttalkFn=kittie.talk;talkFn();constkittie={_sound: 'MEOW',talk(){console.log(this._sound);}}kittie.talk();consttalkFn=kittie.talk.bind(kittie);talkFn();⭐ En una función,
thishace referencia al contexto en el que fue llamada. Si es sólo una función y no un método, entonces su contexto será el objeto global (windowen el browser,globalen Node)
functionshowMeThis(){console.log(this);}- Para forzar el contexto de una función, podemos utilizar
bind
functionshowMeThis(){console.log(this);}constuser={name: 'Ash Ketchum',email: 'ash@pokemonmaster.com'}constshowMeThisUser=showMeThis.bind(user);showMeThisUser();functionshowMeThis(){console.log(`name: ${this.name}, email: ${this.email}`);}constuser={name: 'Ash Ketchum',email: 'ash@pokemonmaster.com',info: showMeThis}user.info();bindes un método deFunction, que retorna una nueva función (setea elthis), con un nuevo contexto... Se acuerdan que en JS las funciones eran funciones y objetos a la vez?
⭐ El valor de
thisdepende del contexto en el cual se llama a una función. Este contexto está dado por un objeto.
- Usando
bindhacemos explícito el contexto - Más info:
bind- MDN
- Las funciones pueden tener 2 tipos de parámetros: explícitos (los que definimos nosotros) e implícitos. Estos últimos son parámetros que las funcionen tienen y nosotros no definimos
// en esta función, a y b son parámetros explícitosfunctionsum(a,b){returna+b;}thises un parámetro implícito que tienen todas las funciones en JS. Hace referencia al contexto actual y por contexto queremos decir un objeto- Por default,
thisno hace referencia al contexto en el que se creó la función, sino al contexto en que fue invocada (salvo que usemos arrow functions) es decir, desde dónde la estamos llamando - Cuando la función es un método de un objeto,
thishace referencia al objeto a la izquierda del.- Este es el comportamiento default de
thisen la mayoría de los lenguajes orientados a objetos
- Este es el comportamiento default de
constball={position: {x: 20,y: 40},color: 'red',size: 2,describe(){console.log(this);}};- Si es una función cualquiera,
thishace referencia al contexto global (objetowindowen el browser yglobalen Node) ⚠️ Recuerden que siempre que entramos a una función, estamos generando un nuevo contexto de ejecución, por eso cambia
// `this` es una referencia al objeto `context`
context.method();
// contexto globalfunctionplayVideo(){console.log(this);}playVideo();functionVideo(title){this._title=title;console.log(this);}constvideo=newVideo('V/H/S');- ❓ Qué pasa si tenemos funciones dentro de algún método, para modularizar el código?
- Quién sería
thisen este caso, si no estamos invocando un método? ❓
- Quién sería
functionUser(name,score){this._name=name;this._score=score;}// a qué hace referencia `this` en este caso?User.prototype.increment=function(){functionaddOne(){this._score++;}addOne();}User.prototype.login=function(){console.log('login');}constuser=newUser("Eva",23);user.increment();⚠️ Recuerden que todas las funciones tienen suthisy que si no le aclaramos cuál es, va a usar el global (window,global)- Este es otro de los conceptos que más confusión generan en JS
- Gran fuente de bugs
- Algo que muy probablemente les pregunten en una entrevista para hacerles caer en la trampa si hablan de objetos en JS
- Guardamos el contexto antes, para desp hacer referencia
functionUser(name,score){this._name=name;this._score=score;}User.prototype.increment=function(){// guardamos el contexto antes de definir la nueva funciónconstself=this;// ahora `self`es una referencia al `this` anteriorfunctionaddOne(){self._score++;}addOne();}User.prototype.login=function(){console.log('login');}constuser=newUser("Eva",23);user.increment();- Forzamos el contexto, usando
bind
functionUser(name,score){this._name=name;this._score=score;}User.prototype.increment=function(){constbindedAddOne=(functionaddOne(){this._score++;}).bind(this);bindedAddOne();}User.prototype.login=function(){console.log('login');}constuser=newUser("Eva",23);user.increment();- Feat. ES6 arrow functions! 🙌:fireworks:
functionUser(name,score){this._name=name;this._score=score;}User.prototype.increment=function(){// el `this` de la función `addOne` va a hacer referencia al valor de `this`en el momento de ser declarada (igual que `self` en la primer solución)constaddOne=()=>this._score++;addOne();}User.prototype.login=function(){console.log('login');}constuser=newUser("Eva",23);user.increment();- Cuando usamos arrow functions,
thises asignado automáticamente al contexto (elthis) dentro del cual la función fue declarada- Esto es lo que se conoce como lexical scoping
- Además de bind, podemos utilizar otros métodos similares como
callyapplypara tomar el control y setear manualmente el valor que se le asigna athis
constobj={normalFn(){console.log(this);},arrowFn: ()=>console.log(this)}obj.normalFn();obj.arrowFn();normalFnes una función común que invocamos como método de un objeto, por eso el valor dethispasa a ser el objeto, peroarrowFnes una arrow function y el valor dethisal momento de definirla eraglobal- Por eso es recomendable usar arrow functions para los callbacks y funciones comunes para definir métodos
- Tampoco podemos forzar el valor de
thisen una arrow function
- Se acuerdan, por ejemplo del parámetro opcional
thisArgdelforEach? - Ahora nos viene bien! 🚀
constvideo={title: 'V/H/S',tags: ['horror','indie','thriller'],showTags(){this.tags.forEach(function(tag){console.log(this.title,tag);})}}video.showTags();constvideo={title: 'V/H/S',tags: ['horror','indie','thriller'],showTags(){this.tags.forEach(function(tag){console.log(this,tag);})}}video.showTags();constvideo={title: 'V/H/S',tags: ['horror','indie','thriller'],showTags(){this.tags.forEach(tag=>console.log(this.title,tag))}}video.showTags();constvideo={title: 'V/H/S',tags: ['horror','indie','thriller'],showTags(){this.tags.forEach(function(tag){console.log(this.title,tags);},this)}}video.showTags();- un parámetro implícito que tienen todas las funciones en JS
- un objeto que representa el contexto actual de ejecución (en el cuál estamos ejecutando una función)
- si es una función común y corriente,
thishace referencia al contexto global (Windowen el browser,globalen Node) - si es un método
mde un objetoxy lo invocamos comox.m(),thishace referencia al objetox - si utilizamos una función constructora, que invocamos usando la keyword
new,thishace referencia al nuevo objeto que creamos - si usamos arrow functions, el valor de
thisestá definido por lo que llamamos lexical scope, es decir,thismantiene el valor que tenía en el lugar donde definimos la función, no se crea un nuevo contexto - hay métodos que tienen un parámetro opcional para setear el valor de
this, por ejemplo algunos de Array - en el caso de ser necesario, podemos forzar el valor de
thisde diversas formas
- Como truco, podemos hacer una analogía con los modos de las cámaras de fotos:
thistiene 3 modos,auto,semiymanual.auto: el valor dethisse setea automáticamente según el contexto (ver ítems 1, 2 y 3)semi: tenemos algo de control sobre el valor dethis, aunque se define de forma implícita, utilizando arrow functions (ver ítem 4)manual: tenemos todo el control y nosotros definimos explícitamente el valor dethis(ver ítems 5 y 6)
functionUser(email,name){this._email=email;this._name=name;}User.prototype.login=function(){console.log(`${this._email} just logged in`);};User.prototype.getEmail=function(){returnthis._email;};User.prototype.getName=function(){returnthis._name;};constuserOne=newUser('ryu@ninjas.com','Ryu');userOne.login();// check logsconsole.log(userOne.__proto__);console.log(User.prototype);console.dir(userOne);console.dir(userOne.__proto__);console.dir(User.prototype);console.log(userOne.__proto__===User.prototype);- Al definir los métodos dentro de una clase, JS se encarga por nosotros de definirlos en el
prototypede la función constructora (ó Factory Function) - Renombramos la parte función del combo función-objeto
Usercomoconstructor Class Useres nuestra vieja y conocida función constructora, con otra sintaxis!
// aplicando un poco de syntax sugar...classUser{constructor(email,name){this._email=email;this._name=name;}login(){console.log(`${this._email} just logged in`);}getEmail(){returnthis._email;}getName(){returnthis._name;}}constuserOne=newUser('ryu@ninjas.com','Ryu');userOne.login();// check logsconsole.log(userOne.__proto__);console.log(User.prototype);console.dir(userOne);console.dir(userOne.__proto__);console.dir(User.prototype);console.log(userOne.__proto__===User.prototype);classMammal{constructor(sound){this._sound=sound;}talk(){returnthis._sound;}}constfluffy=newMammal('woof');fluffy.talk();classMammal{constructor(sound){this._sound=sound;}talk(){returnthis._sound;}}// herenciaclassDogextendsMammal{constructor(){super('woOoOof!');}}constfluffy=newDog();fluffy.talk();// BOOM!console.log(typeofDog);console.log(Dog.prototype.isPrototypeOf(fluffy));superes una keyword que utilizamos para acceder a propiedades y métodos de una superclase, por ejemplo el constructor⚠️ JavaScript no tiene clases! Es sólo sugar syntax sobre lo que ya conocemos de prototipos- ❓ En los ejemplos que vimos recién, cuáles serían los prototipos?
- ⭐ Si usamos
Class, lanewkeyword es requerida para crear nuevos objetos (no pasa si usamos las funciones de siempre y tiene consecuencias sobre elthis)
- Los constructores nos permiten construir e inicializar objetos
- Son funciones, que pueden tomar ciertos argumentos y setearlos como propiedades del nuevo objeto
- Por convención y para distinguirlos de otras funciones, se suele escribir la primer letra en mayúscula
- Los invocamos utilizando la keyword
new
functionPokeBall(size,color){// propsthis._size=size;this._color=color;// methodsthis.getSize=function(){console.log(this._size);};this.getColor=function(){console.log(this._color);}};constultraBall=newPokeBall(3,'black');- Los objetos creados sin un prototipo seteado explícitamente, tendran como prototipo al objeto
Object - El prototipo se setea en la propiedad
prototypede la función constructora
functionPokeBall(size,color){this._size=size;this._color=color;};PokeBall.prototype.getSize=function(){console.log(this._size);}PokeBall.prototype.getColor=function(){console.log(this._color);}constultraBall=newPokeBall(3,'black');// ver las propiedades de la función/objeto constructoraconsole.dir(PokeBall);constprotoPokeBall={getSize(){console.log(this._size);},getColor(){console.log(this._color);}};functionPokeBall(size,color){this._size=size;this._color=color;};PokeBall.prototype=protoPokeBall;constultraBall=newPokeBall(3,'black');// ver las propiedades de la función/objeto constructoraconsole.dir(PokeBall);functionUserCreator(name,score){// creamos un objeto vacío y lo enlazamos con su prototipo seteando su popiedad oculta __proto__constnewUser=Object.create(userFunctions);// seteamos sus propiedadesnewUser.name=name;newUser.score=score;returnnewUser;}// `prototype`: el objeto nuevo va a heredar estas propiedadesconstuserFunctions={increment(){this.score++;},login(){console.log(`${this.name} has logged in`);}}functionpaidUserCreator(paidName,paidScore,accountBalance){constnewPaidUser=UserCreator(paidName,paidScore);Object.setPrototypeOf(newPaidUser,paidUserFunctions);newPaidUser.accountBalance=accountBalance;returnnewPaidUser;}constpaidUserFunctions={increaseBalance(){this.accountBalance++;}};// creamos un nuevo usuario normalconstuser=UserCreator('Sarah Connor',7);// interactuamos con el objeto a través de sus métodosuser.login();// establecemos la cadena de prototiposObject.setPrototypeOf(paidUserFunctions,userFunctions);// creamos un nuevo usuario pagoconstpaidUser=paidUserCreator('Alyssa',8,25);// invocamos métodos del nuevo objeto pagopaidUser.login()paidUser.increaseBalance();console.dir(user);console.dir(paidUser);console.dir(user.__proto__);console.dir(paidUser.__proto__);- Usamos la ya conocida Prototype Chain
- Si usamos
Class, para que una 'clase' (falsa) herede de otra, es decir, sea una subclase, usamosextends- De esta forma, los objetos creados a partir de la 'subclase' (falsa) heredarán propiedades definidas en esta y en la 'superclase'
classDogextendsMammal{constructor(){// llamamos al constructor de la superclasesuper('woOoOof!');}}- Un objeto puede sobreescribir un método de su prototipo y tiene precedencia sobre este otro
constprotoObj={logX(){console.log('x');}}constobj=Object.create(protoObj);obj.logX=function(){console.log('<xXx>');};obj.logX();// '<xXx>'classUser{constructor(email,name){this._email=email;this._name=name;this._score=0;}login(){console.log(`${this._email} just logged in`);}logout(){console.log(`${this._email} just logged out`);}updateScore(){this._score++;console.log(`${this._user}'s score is now ${this._score}`);}}classAdminextendsUser{deleteUser(ripUser){users=users.filter(user=>user.email!==ripUser.email);}}constryu=newUser('ryu@sf2.com','Ryu');constken=newUser('ken@sf2.com','Ken');constadmin=newAdmin('chunli@sf2.com','Chun-Li');constusers=[ryu,ken,admin];console.log(users);admin.deleteUser(ken);console.log(users);functionUser(email,name){this._email=email;this._name=name;}User.prototype.login=function(){console.log(`${this._email} just logged in`);}User.prototype.logout=function(){console.log(`${this._email} just logged out`);}constryu=newUser('ryu@sf2.com','Ryu');constken=newUser('ken@sf2.com','Ken');console.log(ken);ryu.login();- La palabra viene del griego poli (muchos) y morfo (forma), muchas formas
- Definición formal: propiedad que nos permite enviar mensajes sintácticamente iguales (es decir, que se llaman igual y toman los mismos parámetros) a objetos de tipos distintos. El único requisito que deben cumplir los objetos que se utilizan de manera polimórfica es saber responder al mensaje que se les envía
- tl;dr Propiedad que permite que objetos de diferentes tipos/'clases' puedan responder a los mismos mensajes/métodos
- Esto se logra sobreescribiendo un método de una clase en una subclase
- Propiedad que nos permite tratar de la misma forma a objetos de tipos diferentes
- Cuando hablamos de objetos de diferentes tipos en el contexto de polimorfismo, nos referimos a objetos cuyos prototipos son diferentes ó que son (con muchas comillas) 'instancias' de diferentes 'clases'
constUser={active: false,sayHello(){console.log(`${this.name} says hi!`)}};constStudent={name: 'Morty',major: 'JavaScript'};constProfessor={name: 'Rick',teaching: ['JavaScript','NodeJS','Physics']};Object.setPrototypeOf(Student,User);Object.setPrototypeOf(Professor,User);Student.active=true;constnewUsers=[Student,Professor];newUsers.forEach(user=>user.sayHello())constUser={active: false,describe(){console.log(`${this.name} says hi!`)}};constStudent={name: 'Morty',major: 'JavaScript',describe(){console.log(`${this.name} studies ${this.major}`);}};constProfessor={name: 'Rick',teaching: ['JavaScript','NodeJS','Physics'],describe(){console.log(`${this.name} teaches ${this.teaching}`);}};Object.setPrototypeOf(Student,User);Object.setPrototypeOf(Professor,User);Student.active=true;constnewUsers=[Student,Professor];newUsers.forEach(user=>user.describe())classAnimal{constructor(name){this._name=name;}makeSound(){console.log('🔉 Default sound!');}}classDogextendsAnimal{constructor(name){super(name);}makeSound(){console.log('🐶 WoOof!')}}classCatextendsAnimal{constructor(name){super(name);}makeSound(){console.log('🐱 MeowW!')}}constanimal=newAnimal('Doggie');animal.makeSound();constdog=newDog('Beethoven');constcat=newCat('Felix');dog.makeSound();cat.makeSound();constperson={firstName: 'Aquiles',lastName: 'Bailoyo',// the old way...getFullName(){return`${this.firstName}${this.lastName}`}};console.log(person.getFullName());- Contras de usar este approach:
- una vez creado el objeto, sus propiedades
firstNameylastNameson read-only (sólo lectura), no podemos modificar el valor - tenemos que utilizar un método para algo que tal vez estaría bueno tener como el valor de una propiedad común
- una vez creado el objeto, sus propiedades
constperson={firstName: 'Aquiles',lastName: 'Bailoyo',getfullName(){return`${this.firstName}${this.lastName}`}};console.log(person.fullName);constperson={firstName: 'Aquiles',lastName: 'Bailoyo',getfullName(){return`${this.firstName}${this.lastName}`},setfullName(name){constfullName=name.split(' ');this.firstName=fullName[0];this.lastName=fullName[1];console.info(`${name} has been set as person's full name.`)}};// usando el _setter_person.fullName='Armando Paredes';// usando el _getter_console.log(person.fullName);- Los getters y setters son métodos definidos en un objeto o clase, que se ven y utilizamos "como si fueran propiedades"
- Forman parte de la interfaz del objeto, es decir, son públicos
- Son features de ES6/2015+
- La idea es que accedamos y modifiquemos propiedades del objeto de forma segura y controlada, a través de los getters y setters
- Usamos getters para acceder/obtener al valor de una propiedad
- Usamos setters para setear/modificar/mutar el valor de una propiedad
- Son métodos que se definen dentro de una clase y podemos utilizar sin necesidad de instanciarla (crear un objeto a partir de esta clase)
- No son métodos que accedemos y utilizamos directamente desde una instancia (objeto)
- Se definen directamente en el constructor (función constructora ó clase), con la keyword
staticdelante - Se invocan con la sintaxis
Clase.método() - Se suelen utilizar como métodos utilitarios para funcionalidad y operaciones que no tienen que ver directamente con el comportamiento de los objetos
classSquare{constructor(width){this._width=width;this._height=width;}getarea(){returnthis._width**2;}setarea(newArea){this._width=Math.sqrt(newArea);this._height=Math.sqrt(newArea);}staticisEqual(aSquare,anotherSquare){returnaSquare.area===anotherSquare.area;}}constsquareA=newSquare(5);constsquareB=newSquare(7);console.log(Square.isEqual(squareA,squareB));constsquareC=newSquare(5);console.log(Square.isEqual(squareA,squareC));- Objeto: colección de datos/funcionalidades relacionados (variables y funciones), que llamamos propiedades
- Propiedad: par clave-valor que almacenamos en un objeto, donde el valor puede ser algún tipo primitivo de JS u otro objeto
- Método: propiedad de un objeto cuyo valor es una función. Función ligada a un objeto
- Encapsulación: Separación entre la interfaz del objeto y su implementación. Interactuamos con los objetos sólo a través de las propiedades y métodos que nos exponen en su interfaz y no de otra forma
- Herencia: un objeto puede acceder y utilizar propiedades/métodos definidos en su prototipo, o en el prototipo de su prototipo, etc, lo que llamamos su Prototype Chain. Básicamente es una transferencia de propiedades entre objetos, de 'arriba' hacia 'abajo' en la cadena. Es el mecanismo para reutilizar código que nos brinda el paradigma.
- Polimorfismo: propiedad que permite que objetos de diferentes tipos o 'clases' puedan responder a los mismos mensajes/métodos. Esto se logra sobreescribiendo un método de una clase en una subclase y nos permite tratar de la misma forma a objetos de tipos diferentes
for(constkeyinobj){console.log(`key: ${key}, value: ${obj[key]}`);}for(constkeyinobj){if(obj.hasOwnProperty(key)){console.log(`key: ${key}, value: ${obj[key]}`);}}// iterando las keys del objeto original y agregándolas con sus valores a la copiaconstcircle={radius: 1,draw(){console.log('draw');}}constcircleClone={};for(keyincircle){circleClone[key]=circle[key];}constcircle={radius: 1,draw(){console.log('draw');}}constcircleClone=Object.assign({},circle);// con `Object.assign()` también podemos sobreescribir algunas propiedades si le pasamos otro parámetroconstcircleWithBiggerRadius=Object.assign({},circle,{radius: 3});// the ninja wayconstcircle={radius: 1,draw(){console.log('draw');}}constcircleClone={...circle};De MDN:
- Working with Objects - MDN
- Inheritance and the prototype chain - MDN
- Details_of_the_Object_Model - MDN
- The Principles Of Object-oriented Javascript
- Learning JavaScript Design Patterns
- Design Patterns : Elements of Reusable Object-Oriented Software
La idea de usar paradigmas (como POO) es tener herramientas para organizar mejor nuestro código, para que sea más legible, fácil de razonar, mantenible, etc.
Programación Orientada a Objetos es un paradigma de programación que utiliza objetos para modelar entidades del mundo real
En el caso de POO, lo que nos interesa principalmente es encapsular/empaquetar datos relacionados con funciones que podemos aplicar sobre esos datos y dividir nuestro programa en estos objetos, que interactúan entre si a traves de su interfaz, intercambiando mensajes





