4 votes

Si JavaScript ne supporte pas l'héritage classique, pourquoi suis-je capable de créer des constructeurs et d'utiliser le mot-clé new ?

Selon MDN, javascript ne supporte que l'héritage prototypique. Pourtant, je peux faire ce qui suit :

function Human() {

  this.eyes = 2;
  this.feet = 2;

}

Var Mark = new Human();

Ce qui est encore plus déroutant, c'est que je peux ajouter une méthode au constructeur à l'aide de la fonction .prototype mot-clé :

Human.prototype.walk = function(distance) {
  //code
}

Pourtant, il existe un moyen approprié de créer des objets en utilisant Object.Create qui est apparemment le prototype approprié de création d'objets :

var Human = {
  eyes: 2,
  feets: 2

}

var Mark = Object.create(Human);

Quelqu'un peut-il éclaircir ce point pour moi ? Je vous remercie.

3voto

Patrick Roberts Points 405

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 .

1voto

t.niese Points 4623

Vous pouvez utiliser new parce que la spécification du langage l'a défini de cette façon. Les créateurs de JavaScript auraient également pu omettre la possibilité d'utiliser la balise new mot-clé ou Object.create() .


new ne suggère en soi rien sur l'héritage ; il pourrait aussi exister dans des langages sans héritage du tout. Il s'agit simplement d'un mot-clé permettant de créer un nouveau fichier "objet" en JavaScript.

Et selon la langue, new a des significations différentes. Il peut simplement définir la création d'un nouvel objet, mais peut également inclure la signification de l'endroit et de la manière d'allouer la mémoire, et/ou de ce qui est responsable du cycle de vie de la mémoire.

Un langage classique basé sur l'héritage pourrait fonctionner sans un new du tout. Ou il pourrait avoir un new en faveur d'une meilleure façon de créer des objets dans une version plus récente du langage.

On peut imaginer plusieurs façons de créer un nouvel objet à partir d'un descripteur :

  new Descriptor(arg1, arg2);
  Descriptor obj(arg1, arg2);
  obj = Descriptor.alloc().init(arg1, arg2);
  obj = Descriptor.new(arg1, arg2);
  obj = create(Descriptor, arg1, arg2);
  ...

Tous ces éléments peuvent avoir des significations légèrement différentes selon les langues. Il ne faut donc pas trop s'inquiéter si une langue emprunte un mot-clé ou un concept à une autre langue, car la plupart du temps, ils ne diffèrent que par des détails mineurs (voire critiques).

Utilisez donc vos connaissances antérieures pour faciliter l'apprentissage de la nouvelle langue, mais n'essayez pas trop fort de synonymiser parfaitement ces concepts entre différentes langues. Vous devez garder à l'esprit que les autres langues ont des concepts différents même s'ils se ressemblent. Par conséquent, il est souvent utile de simplement les accepter tels qu'ils sont donnés par les spécifications.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X