La première chose que vous devez comprendre est que l'extrait que vous avez fourni comme exemple est toujours un héritage prototypique, et voici pourquoi :
-
Human
est une fonction qui contient un prototype
objet. Les instances de Human
étendent cet objet prototype avec leurs propres données initialisées dans l'objet Human
constructeur.
- Le site
prototype
peut être modifié au moment de l'exécution. Même après avoir créé des instances de la classe, vous pouvez toujours modifier leur comportement hérité en ajoutant ou en changeant les propriétés de la classe. prototype
objet. Rien de tout cela n'est possible avec l'héritage classique.
- Dans l'héritage classique, il y a une différence distincte entre une classe et un objet. Dans l'héritage prototypique, les classes sont simplement un objet qui est une sorte de fonction constructible ce qui signifie qu'il peut être invoqué avec l'option
new
mais sinon, il peut être traité comme n'importe quel autre objet.
Compte tenu de ces informations, montrons quelques similarités et différences essentielles entre Object.create()
y new
:
function Human() {
this.eyes = 2;
this.feet = 2;
}
Human.prototype.walk = function () { };
var josh = new Human();
console.log(josh);
var human = {
constructor: function Human() {
this.eyes = 2;
this.feet = 2;
},
walk: function () { }
};
// create josh with prototype of human
var josh = Object.create(human);
// initialize own properties by calling constructor
human.constructor.call(josh); // or josh.constructor();
console.log(josh);
On ne le dirait pas au premier abord, mais ces deux extraits créent en fait une instance josh
avec le exactement la même disposition :
{
eyes: 2,
feet: 2,
__proto__: {
walk: f (),
constructor: f Human(),
__proto__: Object.prototype
}
}
C'est-à-dire :
var proto = Object.getPrototypeOf(josh);
var protoProto = Object.getPrototypeOf(proto);
console.log(proto === Human.prototype); // or proto === human
console.log(protoProto === Object.prototype);
<- true
<- true
Cela démontre la chaîne des prototypes de josh
. C'est le chemin de l'héritage qui détermine le comportement de l'objet, et montre que josh
hérite de Human
qui hérite de Object
.
La différence entre les deux consoles Stack Snippet ci-dessus s'explique par le fait que l'option constructor
est un non dénombrable propriété de Human.prototype
tandis que le deuxième extrait constructor
est un énumérable propriété de human
.
Si vous voulez analyser le deuxième extrait, je vous conseille vivement de jeter un coup d'œil à la documentation de l'application Object.create()
sur MDN, et peut-être même jeter un coup d'œil à ses spécifications si vous êtes capable de comprendre le langage dense.
Voici comment vous pouvez utiliser Object.create()
avec notre définition de Human
à la place :
function Human() {
this.eyes = 2;
this.feet = 2;
}
Human.prototype.walk = function () { };
// create prototypal inheritance
var josh = Object.create(Human.prototype);
// initialize own properties
Human.call(josh); // or josh.constructor();
console.log(josh);
Ceci initialise les propriétés de l'instance josh
par en appelant le constructeur ES5 avec josh
comme contexte (le this
mot-clé).
Enfin, puisque cela a été mentionné dans les commentaires, tout ceci peut être abstrait pour plus de simplicité en utilisant le langage ES6. class
qui utilise toujours l'héritage prototypique :
class Human {
constructor() {
this.eyes = 2;
this.feet = 2;
}
walk() { }
}
var josh = new Human();
console.log(josh);
La sortie peut sembler différente, mais si vous vérifiez dans la console du développeur réel, vous constaterez que la seule différence dans la mise en forme de l'élément josh
est due au fait que les classes ES6 déclarent des méthodes membres telles que walk()
como non dénombrable les propriétés de Human.prototype
C'est pourquoi il n'apparaît pas dans la console Stack Snippet.
Vous ne pouvez pas utiliser Object.create()
de la même manière que celle démontrée dans l'ES5 car un ES6 class
est seulement constructible (pouvant être invoqué avec new
) et non Appelable (pouvant être invoqué sans new
) :
class Human {
constructor() {
this.eyes = 2;
this.feet = 2;
}
walk() { }
}
var josh = Object.create(Human.prototype); // still works
// no own properties yet because the constructor has not been invoked
console.log(josh);
// cannot call constructor to initialize own properties
Human.call(josh); // josh.constructor(); would not work either
Addendum
J'ai essayé de trouver un moyen de voir plus facilement la chaîne de prototypes d'objets dans la console Stack Snippet, et j'ai donc écrit cette fonction layout()
. Il récure dans la chaîne de prototypes d'un objet et rend toutes les propriétés énumérables. Puisque les chaînes de prototypes ne peuvent pas avoir de cycles, cela ne peut jamais être bloqué dans une récursion infinie :
// makes all properties in an object's prototype chain enumerable
// don't worry about understanding this implementation
function layout (o) {
if (typeof o !== 'object' || !o || o === Object.prototype) return o;
return [...Object.getOwnPropertyNames(o), '__proto__'].reduce(
(a, p) => Object.assign(a, { [p]: layout(o[p]) }),
Object.create(null)
);
}
// this is intentionally one line in order to
// make Stack Snippet Console output readable
function HumanES5() { this.eyes = 2; this.feet = 2; }
HumanES5.prototype.walk = function () { };
var josh = new HumanES5();
console.log(layout(josh));
var josh = Object.create(HumanES5.prototype);
HumanES5.call(josh); // or josh.constructor();
console.log(layout(josh));
class HumanES6 {
constructor () { this.eyes = 2; this.feet = 2; }
walk () { }
}
var josh = new HumanES6();
console.log(layout(josh));
var josh = Object.create(HumanES6.prototype);
// HumanES6.call(josh); will fail, remember?
console.log(layout(josh));
.as-console-wrapper{min-height:100%!important}
Il y a deux choses à noter ici.
- Dans les deux dernières sorties,
class HumanES6 { ... }
fait en fait référence à la constructor
dans la déclaration de la classe. Dans l'héritage prototypique, la classe et son constructeur sont synonymes.
- La dernière sortie n'a pas de propriétés propres.
eyes
y feet
depuis le constructor
n'a jamais été invoqué pour initialiser cette instance de josh
.