Mundo sin la palabra clave "nueva".
Y una sintaxis más simple "en prosa" con Object.create ().
* Este ejemplo se actualiza para las clases de ES6.
En primer lugar, recuerde que Javascript es un lenguaje prototípico . No está basado en clases. Por lo tanto, escribir en forma prototípica expone su verdadera naturaleza y puede ser muy simple, similar a la prosa y poderoso.
TLDR;
const Person = { name: 'Anonymous' } // person has a name
const jack = Object.create(Person) // jack is a person
jack.name = 'Jack' // and has a name 'Jack'
No, no necesitas constructores, no hay new
instanciación ( lee por qué no deberías usarnew
), no super
, no es gracioso __construct
. Simplemente crea objetos y luego los extiende o transforma.
( Si conoce los captadores y establecedores, consulte la sección "Lecturas adicionales" para ver cómo este patrón le ofrece captadores y establecedores gratuitos de una manera que Javascript había pensado originalmente y cuán poderosos son ).
Sintaxis tipo prosa: prototipo base
const Person = {
//attributes
firstName : 'Anonymous',
lastName: 'Anonymous',
birthYear : 0,
type : 'human',
//methods
name() { return this.firstName + ' ' + this.lastName },
greet() {
console.log('Hi, my name is ' + this.name() + ' and I am a ' + this.type + '.' )
},
age() {
// age is a function of birth time.
}
}
const person = Object.create(Person). // that's it!
De un vistazo, se ve muy legible.
Extensión, creando un descendiente de Person
* Los términos correctos son prototypes
, y sus descendants
. No hay classes
y no hay necesidad de hacerlo instances
.
const Skywalker = Object.create(Person)
Skywalker.lastName = 'Skywalker'
const anakin = Object.create(Skywalker)
anakin.firstName = 'Anakin'
anakin.birthYear = '442 BBY'
anakin.gender = 'male' // you can attach new properties.
anakin.greet() // 'Hi, my name is Anakin Skywalker and I am a human.'
Person.isPrototypeOf(Skywalker) // outputs true
Person.isPrototypeOf(anakin) // outputs true
Skywalker.isPrototypeOf(anakin) // outputs true
Una forma de proporcionar una forma "predeterminada" de crear un descendant
, es adjuntando un #create
método:
Skywalker.create = function(firstName, gender, birthYear) {
let skywalker = Object.create(Skywalker)
Object.assign(skywalker, {
firstName,
birthYear,
gender,
lastName: 'Skywalker',
type: 'human'
})
return skywalker
}
const anakin = Skywalker.create('Anakin', 'male', '442 BBY')
Las siguientes formas tienen menor legibilidad:
Compare con el equivalente "clásico":
function Person (firstName, lastName, birthYear, type) {
this.firstName = firstName
this.lastName = lastName
this.birthYear = birthYear
this.type = type
}
// attaching methods
Person.prototype.name = function() { return firstName + ' ' + lastName }
Person.prototype.greet = function() { ... }
Person.prototype.age = function() { ... }
function Skywalker(firstName, birthYear) {
Person.apply(this, [firstName, 'Skywalker', birthYear, 'human'])
}
// confusing re-pointing...
Skywalker.prototype = Person.prototype
Skywalker.prototype.constructor = Skywalker
const anakin = new Skywalker('Anakin', '442 BBY')
Person.isPrototypeOf(anakin) // returns false!
Skywalker.isPrototypeOf(anakin) // returns false!
La legibilidad del código usando el estilo "clásico" no es tan bueno.
Clases ES6
Es cierto que algunos de estos problemas son erradicados por las clases ES6, pero aún así:
class Person {
constructor(firstName, lastName, birthYear, type) {
this.firstName = firstName
this.lastName = lastName
this.birthYear = birthYear
this.type = type
}
name() { return this.firstName + ' ' + this.lastName }
greet() { console.log('Hi, my name is ' + this.name() + ' and I am a ' + this.type + '.' ) }
}
class Skywalker extends Person {
constructor(firstName, birthYear) {
super(firstName, 'Skywalker', birthYear, 'human')
}
}
const anakin = new Skywalker('Anakin', '442 BBY')
// prototype chain inheritance checking is partially fixed.
Person.isPrototypeOf(anakin) // returns false!
Skywalker.isPrototypeOf(anakin) // returns true
Ramificación del prototipo base
// create a `Robot` prototype by extending the `Person` prototype:
const Robot = Object.create(Person)
Robot.type = 'robot'
Robot.variant = '' // add properties for Robot prototype
Adjunte métodos únicos para Robot
// Robots speak in binaries, so we need a different greet function:
Robot.machineGreet = function() { /*some function to convert strings to binary */ }
// morphing the `Robot` object doesn't affect `Person` prototypes
anakin.greet() // 'Hi, my name is Anakin Skywalker and I am a human.'
anakin.machineGreet() // error
Comprobando herencia
Person.isPrototypeOf(Robot) // outputs true
Robot.isPrototypeOf(Skywalker) // outputs false
¡Ya tienes todo lo que necesitas! Sin constructores, sin instanciación. Prosa limpia y clara.
Otras lecturas
¡Posibilidad de escritura, configurabilidad y getters y setters gratuitos!
Para getters y setters gratuitos, o configuración adicional, puede usar el segundo argumento de Object.create () aka propertiesObject. También está disponible en # Object.defineProperty y # Object.defineProperties .
Para ilustrar cuán poderoso es esto, supongamos que queremos que todos Robot
estén hechos estrictamente de metal (a través de writable: false
) y estandarizar los powerConsumption
valores (a través de getters y setters).
const Robot = Object.create(Person, {
// define your property attributes
madeOf: {
value: "metal",
writable: false,
configurable: false,
enumerable: true
},
// getters and setters, how javascript had (naturally) intended.
powerConsumption: {
get() { return this._powerConsumption },
set(value) {
if (value.indexOf('MWh')) return this._powerConsumption = value.replace('M', ',000k')
this._powerConsumption = value
throw new Error('Power consumption format not recognised.')
}
}
})
const newRobot = Object.create(Robot)
newRobot.powerConsumption = '5MWh'
console.log(newRobot.powerConsumption) // outputs 5,000kWh
Y todos los prototipos de Robot
no pueden ser madeOf
otra cosa porque writable: false
.
const polymerRobot = Object.create(Robot)
polymerRobot.madeOf = 'polymer'
console.log(polymerRobot.madeOf) // outputs 'metal'
Mixins (usando # Object.assign) - Anakin Skywalker
¿Puedes sentir a dónde va esto ...?
const darthVader = Object.create(anakin)
// for brevity, property assignments are skipped because you get the point by now.
Object.assign(darthVader, Robot)
Darth Vader obtiene los métodos de Robot
:
darthVader.greet() // inherited from `Person`, outputs "Hi, my name is Darth Vader..."
darthVader.machineGreet() // inherited from `Robot`, outputs 001010011010...
Junto con otras cosas extrañas:
console.log(darthVader.type) // outputs robot.
Robot.isPrototypeOf(darthVader) // returns false.
Person.isPrototypeOf(darthVader) // returns true.
Bueno, si Darth Vader es hombre o máquina es realmente subjetivo:
"Ahora es más máquina que hombre, retorcido y malvado". - Obi-Wan Kenobi
"Sé que hay algo bueno en ti". - Luke Skywalker
Extra: sintaxis ligeramente más corta con # Object.assign
Con toda probabilidad, este patrón acorta su sintaxis. Pero ES6 # Object.assign puede acortar un poco más (para usar Polyfill en navegadores antiguos, consulte MDN en ES6 ).
//instead of this
const Robot = Object.create(Person)
Robot.name = "Robot"
Robot.madeOf = "metal"
//you can do this
const Robot = Object.create(Person)
Object.assign(Robot, {
name: "Robot",
madeOf: "metal"
// for brevity, you can imagine a long list will save more code.
})