Monde sans le mot-clé «nouveau».
Et une syntaxe plus simple de type "prose" avec Object.create ().
* Cet exemple est mis à jour pour les classes ES6.
Tout d'abord, rappelez-vous que Javascript est un langage prototypique . Ce n'est pas basé sur la classe. Par conséquent, l'écriture sous forme prototypique expose sa vraie nature et peut être très simple, semblable à une prose et puissante.
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'
Non, vous n'avez pas besoin de constructeurs, pas d' new
instanciation ( lisez pourquoi vous ne devriez pas utilisernew
), non super
, pas de drôle de drôle __construct
. Vous créez simplement des objets, puis les étendez ou les transformez.
( Si vous connaissez les getters et les setters, consultez la section "Lectures supplémentaires" pour voir comment ce modèle vous donne des getters et des setters gratuits comme Javascript l'avait initialement prévu, et à quel point ils sont puissants .)
Syntaxe de type prose: Protoype de 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!
En un coup d'œil, semble très lisible.
Extension, création d'un descendant de Person
* Les termes corrects sont prototypes
, et leur descendants
. Il n'y a pas classes
et pas besoin de 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
Une façon de fournir un moyen "par défaut" de créer un descendant
, est de joindre une #create
méthode:
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')
Les moyens ci-dessous ont une lisibilité inférieure:
Comparer à l'équivalent "classique":
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 lisibilité du code en utilisant un style "classique" n'est pas très bonne.
Classes ES6
Certes, certains de ces problèmes sont éradiqués par les classes ES6, mais quand même:
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
Branchement du prototype de 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
Joindre des méthodes uniques à 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
Vérification de l'héritage
Person.isPrototypeOf(Robot) // outputs true
Robot.isPrototypeOf(Skywalker) // outputs false
Vous avez déjà tout ce dont vous avez besoin! Pas de constructeur, pas d'instanciation. Prose propre et claire.
Lectures complémentaires
Écriture, configurabilité et Getters et Setters gratuits!
Pour les getters et les setters gratuits, ou pour une configuration supplémentaire, vous pouvez utiliser le deuxième argument d'Object.create () aka propertiesObject. Il est également disponible dans # Object.defineProperty et # Object.defineProperties .
Pour illustrer à quel point cela est puissant, supposons que nous voulions tous Robot
être strictement faits de métal (via writable: false
) et normaliser les powerConsumption
valeurs (via des getters et des 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
Et tous les prototypes de Robot
ne peuvent pas être madeOf
autre chose parce que writable: false
.
const polymerRobot = Object.create(Robot)
polymerRobot.madeOf = 'polymer'
console.log(polymerRobot.madeOf) // outputs 'metal'
Mixins (en utilisant # Object.assign) - Anakin Skywalker
Pouvez-vous sentir où cela va ...?
const darthVader = Object.create(anakin)
// for brevity, property assignments are skipped because you get the point by now.
Object.assign(darthVader, Robot)
Dark Vador obtient les méthodes de Robot
:
darthVader.greet() // inherited from `Person`, outputs "Hi, my name is Darth Vader..."
darthVader.machineGreet() // inherited from `Robot`, outputs 001010011010...
Avec d'autres choses étranges:
console.log(darthVader.type) // outputs robot.
Robot.isPrototypeOf(darthVader) // returns false.
Person.isPrototypeOf(darthVader) // returns true.
Eh bien, que Dark Vador soit un homme ou une machine est en effet subjectif:
«Il est plus une machine maintenant qu'un homme, tordu et méchant. - Obi Wan Kenobi
"Je sais qu'il y a du bien en toi." - Luke Skywalker
Extra - Syntaxe légèrement plus courte avec # Object.assign
Selon toute vraisemblance, ce modèle raccourcit votre syntaxe. Mais ES6 # Object.assign peut raccourcir un peu plus (pour polyfill à utiliser sur les navigateurs plus anciens, voir MDN sur 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.
})