Los prototipos son una característica fundamental de JavaScript que permite la herencia entre objetos. Cada objeto en JavaScript tiene un prototipo, que es otro objeto del cual hereda propiedades y métodos.
NotaEjemplo de la vida real: Un prototipo es como el manual de un coche de la misma marca y modelo: cada coche es distinto y tiene su matrícula única, pero todos consultan el mismo manual cuando hay que saber cómo funciona algo.
A diferencia de otros lenguajes que usan clases, JavaScript usa prototipos para la herencia.
Objeto (instancia) Prototipo
┌─────────────┐ ┌─────────────┐
│ name: "Ana" │ ──────▶│ walk() │
│ age: 25 │ │ talk() │
│ __proto__ │ │ __proto__ │──▶ null
└─────────────┘ └─────────────┘
Proceso de búsqueda: Cuando intentas acceder a una propiedad, JavaScript:
null// Objeto padre (prototipo)
const animal = {
tipo: 'Mamífero',
respirar() {
console.log('Respirando...')
}
}
// Creamos un objeto que hereda de animal
const perro = Object.create(animal)
perro.raza = 'Labrador'
perro.ladrar = function () {
console.log('¡Guau!')
}
// Propiedades propias vs heredadas
console.log(perro.raza) // "Labrador" (propio)
console.log(perro.tipo) // "Mamífero" (heredado)
perro.ladrar() // "¡Guau!" (propio)
perro.respirar() // "Respirando..." (heredado)
La cadena de prototipos se ve así:
perro animal Object.prototype
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ raza │ ────▶│ tipo │ ────▶ │ toString() │──▶ null
│ ladrar() │ │ respirar() │ │ valueOf() │
│ __proto__ │ │ __proto__ │ │ __proto__ │
└─────────────┘ └─────────────┘ └─────────────┘
Object.create() es la manera recomendada de crear herencia. Le pasaríamos como primer parámetro el prototipo del objeto que queremos crear.
const vehiculo = {
acelerar() {
console.log('Acelerando...')
}
}
// Herencia con Object.create()
const coche = Object.create(vehiculo)
coche.ruedas = 4
coche.arrancar = function () {
console.log('Motor encendido')
}
const moto = Object.create(vehiculo)
moto.ruedas = 2
// Ambos heredan de vehiculo
coche.acelerar() // "Acelerando..." (heredado)
moto.acelerar() // "Acelerando..." (heredado)
Cuando creas un objeto con {}, automáticamente hereda de Object.prototype. Pero si quieres crear un objeto sin prototipo, puedes usar Object.create(null), de esta forma no heredará de Object.prototype y no tendrás los métodos y propiedades que hereda por defecto.
// Cuando creas un objeto con {} automáticamente hereda de Object.prototype
const objeto = {}
console.log(objeto.toString()) // [object Object] (heredado)
// Para crear un objeto sin prototipo
const sinPrototipo = Object.create(null)
console.log(sinPrototipo.toString()) // TypeError: sinPrototipo.toString is not a function
Object.prototype ya tiene algunos métodos que hereda por defecto, como toString(), hasOwnProperty(), isPrototypeOf(), etc.
Nota⚠️ No uses
__proto__en tu código. Es solo por compatibilidad histórica. Usa siempre Object.getPrototypeOf y Object.setPrototypeOf. Más adelante te lo explico.
Al crear un objeto así { name: 'Ana' } si inspeccionamos el objeto en las herramientas de desarrollo de Chrome, vamos a ver un montón de métodos y propiedades que tiene:
[[Prototype]]: Object
- constructor: ƒ Object()
- hasOwnProperty: ƒ hasOwnProperty()
- isPrototypeOf: ƒ isPrototypeOf()
- propertyIsEnumerable: ƒ propertyIsEnumerable()
- toLocaleString: ƒ toLocaleString()
- toString: ƒ toString()
- valueOf: ƒ valueOf()
- __defineGetter__: ƒ __defineGetter__()
- __defineSetter__: ƒ __defineSetter__()
- __lookupGetter__: ƒ __lookupGetter__()
- __lookupSetter__: ƒ __lookupSetter__()
- __proto__: Object
- get __proto__: ƒ __proto__()
- set __proto__: ƒ __proto__()
Todos estos métodos y propiedades son heredados de Object.prototype. Ahora bien, hay una propiedad especialmente interesante: __proto__.
Esta propiedad apunta al prototipo del objeto. En el caso de que el objeto no tenga un prototipo, esta propiedad será null.
También podemos modificar el prototipo de un objeto con esta propiedad pero no es recomendable.
De hecho, esta propiedad sólo se usa por temas históricos, a día de hoy está totalmente desaconsejada ya que existe una forma más moderna de recuperar el prototipo de un objeto o modificarlo.
Con Object.getPrototypeOf() podemos obtener el prototipo de un objeto.
const objeto = { nombre: 'Ana' }
console.log(Object.getPrototypeOf(objeto)) // Object.prototype
const perro = Object.create(animal)
console.log(Object.getPrototypeOf(perro)) // animal
const vacio = Object.create(null)
console.log(Object.getPrototypeOf(vacio)) // null
Con Object.setPrototypeOf() podemos modificar el prototipo de un objeto.
const persona = { nombre: 'Ana' }
Object.setPrototypeOf(persona, null)
// ahora persona no hereda de Object.prototype
console.log(Object.getPrototypeOf(persona)) // null
const perro = Object.create(persona)
console.log(Object.getPrototypeOf(perro)) // persona
// ¡nos hemos equivocado! Vamos a cambiarlo
Object.setPrototypeOf(perro, animal)
console.log(Object.getPrototypeOf(perro)) // animal
Object.create(null).Object.create() es la forma moderna de crear objetos con un prototipo específico.Object.getPrototypeOf() y Object.setPrototypeOf() en lugar de __proto__.