👉 Ver todas las notas
- Objetos
- POO
- Prototype
- Las funciones son funciones... y objetos
- Factory Function vs constructor
- isPrototypeOf,- getPrototypeOfe- instanceof
- 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
// 1
const ballXPosition = 20;
const ballYPosition = 40;
const ballColor = 'red';
const ballSize = 2;
// 2
const ball = {
  xPosition: 20,
  yPosition: 40,
  color: 'red',
  size: 2
};
// 3
const ball = {
  position: {
    x: 20,
    y: 40
  },
  color: 'red',
  size: 2
};- Usando notación de ES6 para los métodos, se puede simplificar un poco
const userOne = {
  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
const array = [1, 2, 3];
array.length            // length property
array.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 
const obj = {};  // empty object
const myCar = {
  make: "Ford",
  model: "Mustang"
}const obj = new Object();  // empty object 
const myCar = new Object();
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)
const aTeletubbie = {
  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 
const cat = {
  sound: 'meow!'
}
cat.talk();const cat = {
  sound: 'meow!'
}
const animal = {
  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
function Dog() {}
Dog.prototype.breed = 'Bulldog';
const myDog = new Dog();
myDog.breed
myDog.__proto__
// prototype sólo existe en las funciones
myDog.prototype
Dog.prototype
function Giraffe() {}
Giraffe.prototype
const koala = {};
// prototype es una propiedad que contiene un objeto
koala.prototype
koala.__proto__
// __proto__ es una referencia, no un objeto
koala.__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 constructora
const sum = new Function('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, 
function multiplyBy2(num) {
  return num * 2;
}
multiplyBy2.stored = 5;
multiplyBy2(3); // 6
multiplyBy2.stored; // 5
multiplyBy2.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
function Person(firstName, lastName, age) {
  const person = Object.create();
  // usamos `person`en lugar de `this` porque en este caso `this` no refiere al objeto nuevo que creamos, sino al global
  person.firstName = firstName;
  person.lastName = lastName;
  person.age = age;
  return person;
}
const person = 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 el- thisque utilizan no puede hacer referencia a un nuevo objeto creado
function Person(firstName, lastName, age) {
  this.firstName = firstName;
  this.lastName = lastName;
  this.age = age;
}
const person = new Person('Dare', 'Devil', 32);function Professor(name, teaching, subjects) {
  this.name = name;
  this.isTeaching = teaching;
  this.subjects = subjects;
}
Professor.prototype = {
  showSubjects() {
    console.log(this.subjects);
  }
}
const professorX = new Professor('Charles Xavier', true, ['telepathy', 'leadership']);
console.log(Professor.prototype.isPrototypeOf(professorX));
console.log(Object.getPrototypeOf(professorX));
console.log(professorX instanceof Professor);- isPrototypeOf: sirve para chequear si un objeto es prototipo de otro ó es la clase que se usó para crearlo
- getPrototypeOf: retorna el prototipo (objeto) de un objeto
- instanceof: 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
const animal = {
  init(sound) {
    this._sound = sound;
    return this;
  },
  talk() {
    console.log(this._sound);
  }
}
const cat = 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
function UserCreator(name, score) {
  // creamos un objeto vacío y lo enlazamos con su prototipo seteando su popiedad oculta __proto__
  const newUser = Object.create(userFunctions);
  // seteamos sus propiedades
  newUser.name = name;
  newUser.score = score;
  
  return newUser;
}
// `prototype`: el objeto nuevo va a heredar estas propiedades
const userFunctions = {
  increment() {
    this.score++;
  },
  login() {
    console.log('You have logged in');
  }
}
// creamos un nuevo usuario
const user = UserCreator('Sarah Connor', 7);
// interactuamos con el usuario a través de sus métodos
user.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 asigna- Objectcomo prototipo
- newen 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
const x = {
  prop1: ...,
  prop2: ...,
  ...
};
const object = Object.create() // prototipo de object : Object
const anotherObject = Object.create(x) // prototipo de anotherObject: x
const newObject = new SomeConstructor(); // => prototipo de newObject: SomeConstructor.prototypeconst kittie = {
  _sound: 'MEOW',
  talk() {
    console.log(this._sound);
  }
}
kittie.talk();
const talkFn = kittie.talk;
talkFn();const kittie = {
  _sound: 'MEOW',
  talk() {
    console.log(this._sound);
  }
}
kittie.talk();
const talkFn = 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)
function showMeThis() {
  console.log(this);
}- Para forzar el contexto de una función, podemos utilizar bind
function showMeThis() {
  console.log(this);
}
const user = {
  name: 'Ash Ketchum',
  email: 'ash@pokemonmaster.com'
}
const showMeThisUser = showMeThis.bind(user);
showMeThisUser();function showMeThis() {
  console.log(`name: ${this.name}, email: ${this.email}`);
}
const user = {
  name: 'Ash Ketchum',
  email: 'ash@pokemonmaster.com',
  info: showMeThis
}
user.info();- bindes un método de- Function, que retorna una nueva función (setea el- this), 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ícitos
function sum(a, b) {
  return a + 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 
const ball = {
  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 global
function playVideo() {
  console.log(this);
}
playVideo();function Video(title) {
  this._title = title;
  console.log(this);
}
const video = new Video('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 
function User(name, score) {
  this._name = name;
  this._score = score;
}
// a qué hace referencia `this` en este caso?
User.prototype.increment = function() {
  function addOne() {
    this._score++;
  }
  addOne();
} 
User.prototype.login = function() {
  console.log('login');
}
const user = new User("Eva", 23);
user.increment();- ⚠️ Recuerden que todas las funciones tienen su- thisy 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
function User(name, score) {
  this._name = name;
  this._score = score;
}
User.prototype.increment = function() {
  // guardamos el contexto antes de definir la nueva función
  const self = this;
  
  // ahora `self`es una referencia al `this` anterior
  function addOne() {
    self._score++;
  }
  addOne();
} 
User.prototype.login = function() {
  console.log('login');
}
const user = new User("Eva", 23);
user.increment();- Forzamos el contexto, usando bind
function User(name, score) {
  this._name = name;
  this._score = score;
}
User.prototype.increment = function() {
  const bindedAddOne = (function addOne() {
    this._score++;
  }).bind(this);
  bindedAddOne();
} 
User.prototype.login = function() {
  console.log('login');
}
const user = new User("Eva", 23);
user.increment();- Feat. ES6 arrow functions! 🙌:fireworks:
function User(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)
  const addOne = () => this._score++;
  addOne();
} 
User.prototype.login = function() {
  console.log('login');
}
const user = new User("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
const obj = {
  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 de- thispasa a ser el objeto, pero- arrowFnes una arrow function y el valor de- thisal momento de definirla era- global
- 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! 🚀
const video = {
  title: 'V/H/S',
  tags: ['horror', 'indie', 'thriller'],
  showTags() {
    this.tags.forEach(function(tag) {
      console.log(this.title, tag);
    })
  }
}
video.showTags();const video = {
  title: 'V/H/S',
  tags: ['horror', 'indie', 'thriller'],
  showTags() {
    this.tags.forEach(function(tag) {
      console.log(this, tag);
    })
  }
}
video.showTags();const video = {
  title: 'V/H/S',
  tags: ['horror', 'indie', 'thriller'],
  showTags() {
    this.tags.forEach(tag => console.log(this.title, tag))
  }
}
video.showTags();const video = {
  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 de- thisse setea automáticamente según el contexto (ver ítems 1, 2 y 3)
- semi: tenemos algo de control sobre el valor de- this, 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 de- this(ver ítems 5 y 6)
 
function User(email, name) {
  this._email = email;
  this._name = name;
}
User.prototype.login = function() {
  console.log(`${this._email} just logged in`);
};
User.prototype.getEmail = function() {
  return this._email;
};
User.prototype.getName = function() {
  return this._name;
};
const userOne = new User('ryu@ninjas.com', 'Ryu');
userOne.login();
// check logs
console.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...
class User {
  constructor(email, name) {
    this._email = email;
    this._name = name;
  }
  login() {
    console.log(`${this._email} just logged in`);
  }
  getEmail() {
    return this._email;
  }
  getName() {
    return this._name;
  }
}
const userOne = new User('ryu@ninjas.com', 'Ryu');
userOne.login();
// check logs
console.log(userOne.__proto__);
console.log(User.prototype);
console.dir(userOne);
console.dir(userOne.__proto__);
console.dir(User.prototype);
console.log(userOne.__proto__ === User.prototype);class Mammal {
  constructor(sound) {
    this._sound = sound;
  }
  talk() {
    return this._sound;
  }
}
const fluffy = new Mammal('woof');
fluffy.talk();class Mammal {
  constructor(sound) {
    this._sound = sound;
  }
  talk() {
    return this._sound;
  }
}
// herencia
class Dog extends Mammal {
  constructor() {
    super('woOoOof!');
  }
}
const fluffy = new Dog();
fluffy.talk();
// BOOM!
console.log(typeof Dog);
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
function PokeBall(size, color) {
   // props
   this._size = size;
   this._color = color;
   // methods
   this.getSize = function() {
     console.log(this._size);
   };
   this.getColor = function() {
     console.log(this._color);
   }
};
const ultraBall = new PokeBall(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
function PokeBall(size, color) {
   this._size = size;
   this._color = color;
};
PokeBall.prototype.getSize = function() {
  console.log(this._size);
}
PokeBall.prototype.getColor = function() {
  console.log(this._color);
}
const ultraBall = new PokeBall(3, 'black');
// ver las propiedades de la función/objeto constructora
console.dir(PokeBall);const protoPokeBall = {
  getSize() {
    console.log(this._size);
  },
  getColor() {
    console.log(this._color);
  }
};
function PokeBall(size, color) {
   this._size = size;
   this._color = color;
};
PokeBall.prototype = protoPokeBall;
const ultraBall = new PokeBall(3, 'black');
// ver las propiedades de la función/objeto constructora
console.dir(PokeBall);function UserCreator(name, score) {
  // creamos un objeto vacío y lo enlazamos con su prototipo seteando su popiedad oculta __proto__
  const newUser = Object.create(userFunctions);
  // seteamos sus propiedades
  newUser.name = name;
  newUser.score = score;
  
  return newUser;
}
// `prototype`: el objeto nuevo va a heredar estas propiedades
const userFunctions = {
  increment() {
    this.score++;
  },
  login() {
    console.log(`${this.name} has logged in`);
  }
}
function paidUserCreator(paidName, paidScore, accountBalance) {
  const newPaidUser = UserCreator(paidName, paidScore);
  Object.setPrototypeOf(newPaidUser, paidUserFunctions);
  newPaidUser.accountBalance = accountBalance;
  return newPaidUser;
}
const paidUserFunctions = {
  increaseBalance() {
    this.accountBalance++;
  }
};
// creamos un nuevo usuario normal
const user = UserCreator('Sarah Connor', 7);
// interactuamos con el objeto a través de sus métodos
user.login();
// establecemos la cadena de prototipos
Object.setPrototypeOf(paidUserFunctions, userFunctions);
// creamos un nuevo usuario pago
const paidUser = paidUserCreator('Alyssa', 8, 25);
// invocamos métodos del nuevo objeto pago
paidUser.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'
 
class Dog extends Mammal {
  constructor() {
    // llamamos al constructor de la superclase
    super('woOoOof!');
  }
}- Un objeto puede sobreescribir un método de su prototipo y tiene precedencia sobre este otro
const protoObj = {
  logX() {
    console.log('x');
  }
}
const obj = Object.create(protoObj);
obj.logX = function() {
  console.log('<xXx>');
};
obj.logX(); // '<xXx>'class User {
  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}`);
  }
}
class Admin extends User {
  deleteUser(ripUser) {
    users = users.filter(user => user.email !== ripUser.email);
  }
}
const ryu = new User('ryu@sf2.com', 'Ryu');
const ken = new User('ken@sf2.com', 'Ken');
const admin = new Admin('chunli@sf2.com', 'Chun-Li');
const users = [ryu, ken, admin];
console.log(users);
admin.deleteUser(ken);
console.log(users);function User(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`);
}
const ryu = new User('ryu@sf2.com', 'Ryu');
const ken = new User('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'
const User = {
  active: false,
  sayHello() {
    console.log(`${this.name} says hi!`)
  }
};
const Student = {
  name: 'Morty',
  major: 'JavaScript'
};
const Professor = {
  name: 'Rick',
  teaching: ['JavaScript', 'NodeJS', 'Physics']
};
Object.setPrototypeOf(Student, User);
Object.setPrototypeOf(Professor, User);
Student.active = true;
const newUsers = [Student, Professor];
newUsers.forEach(user => user.sayHello())const User = {
  active: false,
  describe() {
    console.log(`${this.name} says hi!`)
  }
};
const Student = {
  name: 'Morty',
  major: 'JavaScript',
  describe() {
    console.log(`${this.name} studies ${this.major}`);
  }
};
const Professor = {
  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;
const newUsers = [Student, Professor];
newUsers.forEach(user => user.describe())class Animal {
  constructor(name) {
    this._name = name;
  }
  makeSound() {
    console.log('🔉 Default sound!');
  }
}
class Dog extends Animal {
  constructor(name) {
    super(name);
  }
  makeSound() {
    console.log('🐶 WoOof!')
  }
}
class Cat extends Animal {
  constructor(name) {
    super(name);
  }
  makeSound() {
    console.log('🐱 MeowW!')
  }
}
const animal = new Animal('Doggie');
animal.makeSound();
const dog = new Dog('Beethoven');
const cat = new Cat('Felix');
dog.makeSound();
cat.makeSound();const person = {
  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 
const person = {
  firstName: 'Aquiles',
  lastName: 'Bailoyo',
  get fullName() {
    return `${this.firstName} ${this.lastName}`
  }
};
console.log(person.fullName);const person = {
  firstName: 'Aquiles',
  lastName: 'Bailoyo',
  get fullName() {
    return `${this.firstName} ${this.lastName}`
  },
  set fullName(name) {
    const fullName = 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
class Square {
  constructor(width) {
    this._width = width;
    this._height = width;
  }
  get area() {
    return this._width ** 2;
  }
  set area(newArea) {
    this._width = Math.sqrt(newArea);
    this._height = Math.sqrt(newArea);
  }
  
  static isEqual(aSquare, anotherSquare) {
    return aSquare.area === anotherSquare.area;
  }
}
const squareA = new Square(5);
const squareB = new Square(7);
console.log(Square.isEqual(squareA, squareB));
const squareC = new Square(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 (const key in obj) {
  console.log(`key: ${key}, value: ${obj[key]}`);
}for (const key in obj) {
  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 copia
const circle = {
  radius: 1,
  draw() {
    console.log('draw');
  }
}
const circleClone = {};
for (key in circle) {
  circleClone[key] = circle[key];
}const circle = {
  radius: 1,
  draw() {
    console.log('draw');
  }
}
const circleClone = Object.assign({}, circle);
// con `Object.assign()` también podemos sobreescribir algunas propiedades si le pasamos otro parámetro
const circleWithBiggerRadius = Object.assign({}, circle, { radius: 3 });// the ninja way
const circle = {
  radius: 1,
  draw() {
    console.log('draw');
  }
}
const circleClone = {...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





