36 votes

Héritage JavaScript et propriété du constructeur

Considérons le code suivant.

function a() {}
function b() {}
function c() {}

b.prototype = new a();
c.prototype = new b();

console.log((new a()).constructor); //a()
console.log((new b()).constructor); //a()
console.log((new c()).constructor); //a()
  • Pourquoi le constructeur n'est-il pas mis à jour pour b et c ?
  • Est-ce que je fais mal l'héritage ?
  • Quelle est la meilleure façon de mettre à jour le constructeur ?

En outre, veuillez considérer les points suivants.

console.log(new a() instanceof a); //true
console.log(new b() instanceof b); //true
console.log(new c() instanceof c); //true
  • Étant donné que (new c()).constructor est égal à a() y Object.getPrototypeOf(new c()) es a{ } comment est-il possible pour instanceof de savoir que new c() est une instance de c ?

http://jsfiddle.net/ezZr5/

64voto

Aadit M Shah Points 17951

Ok, jouons à un petit jeu d'esprit :

De l'image ci-dessus nous pouvons voir :

  1. Lorsque nous créons une fonction comme function Foo() {} JavaScript crée un Function instance.
  2. Chaque Function (la fonction constructeur) possède une propriété prototype qui est un pointeur.
  3. El prototype de la fonction constructeur pointe vers son objet prototype.
  4. L'objet prototype possède une propriété constructor qui est également un pointeur.
  5. El constructor de l'objet prototype renvoie à sa fonction de construction.
  6. Lorsque nous créons une nouvelle instance de Foo comme new Foo() JavaScript crée un nouvel objet.
  7. L'interne [[proto]] de l'instance pointe vers le prototype du constructeur.

Maintenant, la question se pose de savoir pourquoi JavaScript n'attache pas l'élément constructor à l'objet d'instance au lieu du prototype. Pensez-y :

function defclass(prototype) {
    var constructor = prototype.constructor;
    constructor.prototype = prototype;
    return constructor;
}

var Square = defclass({
    constructor: function (side) {
        this.side = side;
    },
    area: function () {
        return this.side * this.side;
    }
});

var square = new Square(10);

alert(square.area()); // 100

Comme vous pouvez le constater, le constructor est simplement une autre méthode du prototype, comme la propriété area dans l'exemple ci-dessus. Ce qui fait que le constructor La particularité de cette propriété est qu'elle est utilisée pour initialiser une instance du prototype. Sinon, c'est exactement la même chose que n'importe quelle autre méthode du prototype.

Définir le constructor sur le prototype est avantageux pour les raisons suivantes :

  1. C'est logiquement correct. Par exemple, considérez Object.prototype . Le site constructor propriété de Object.prototype Les points suivants Object . Si le constructor a été définie sur l'instance, alors Object.prototype.constructor serait undefined parce que Object.prototype est une instance de null .
  2. Elle n'est pas traitée différemment des autres méthodes de prototypage. Cela rend le travail de new plus facile, puisqu'il n'est pas nécessaire de définir l'option constructor sur chaque instance.
  3. Chaque instance partage le même constructor propriété. D'où son efficacité.

Maintenant, lorsque nous parlons d'héritage, nous avons le scénario suivant :

De l'image ci-dessus nous pouvons voir :

  1. Le constructeur dérivé prototype est définie comme l'instance du constructeur de base.
  2. Ainsi, l'interne [[proto]] de l'instance du constructeur dérivé pointe également vers lui.
  3. Ainsi, le constructor de l'instance du constructeur dérivé pointe maintenant vers le constructeur de base.

Quant à la instanceof contrairement à ce que l'on croit, il ne dépend pas de l'opérateur constructor de l'instance. Comme nous pouvons le voir ci-dessus, cela conduirait à des résultats erronés.

El instanceof est un opérateur binaire (il a deux opérandes). Il opère sur un objet instance et une fonction constructeur. Comme expliqué sur Réseau de développeurs Mozilla il fait simplement ce qui suit :

function instanceOf(object, constructor) {
    while (object != null) {
        if (object == constructor.prototype) { //object is instanceof constructor
            return true;
        } else if (typeof object == 'xml') { //workaround for XML objects
            return constructor.prototype == XML.prototype;
        }
        object = object.__proto__; //traverse the prototype chain
    }
    return false; //object is not instanceof constructor
}

Pour le dire simplement, si Foo hérite de Bar alors la chaîne du prototype de l'instance de Foo serait :

  1. foo.__proto__ === Foo.prototype
  2. foo.__proto__.__proto__ === Bar.prototype
  3. foo.__proto__.__proto__.__proto__ === Object.prototype
  4. foo.__proto__.__proto__.__proto__.__proto__ === null

Comme vous pouvez le constater, chaque objet hérite de l'objet Object Constructeur. La chaîne de prototypes se termine lorsqu'un constructeur interne [[proto]] indique que la propriété null .

El instanceof parcourt simplement la chaîne de prototypes de l'objet d'instance (le premier opérande) et compare la chaîne interne [[proto]] de chaque objet à la propriété prototype de la fonction constructeur (le second opérande). S'ils correspondent, il retourne true ; et sinon, si la chaîne de prototypes se termine, il retourne false .

12voto

pimvdb Points 66332

Par défaut,

function b() {}

puis b.prototype a un .constructor qui a pour valeur b automatiquement. Cependant, vous êtes en train d'écraser le prototype et donc d'écarter cette variable :

b.prototype = new a;

Puis b.prototype n'a pas de .constructor plus de propriété ; elle a été effacée avec l'écrasement. C'est fait hériter de a cependant, et (new a).constructor === a et donc (new b).constructor === a (il fait référence à la même propriété dans la chaîne de prototypes).

Le mieux est de le régler manuellement par la suite :

b.prototype.constructor = b;

Vous pourriez également créer une petite fonction pour cela :

function inherit(what, from) {
    what.prototype = new from;
    what.prototype.constructor = what;
}

http://jsfiddle.net/79xTg/5/

5voto

Christoph Points 64389

constructor est une propriété régulière et non énumérable de la valeur par défaut de l'élément prototype des objets fonctionnels. Ainsi, l'affectation à prototype perdront la propriété.

instanceof fonctionnera toujours puisqu'il n'utilise pas constructor mais parcourt plutôt la chaîne de prototypes de l'objet à la recherche de la valeur (actuelle) de la fonction prototype la propriété, c'est-à-dire foo instanceof Foo est équivalent à

var proto = Object.getPrototypeOf(foo);
for(; proto !== null; proto = Object.getPrototypeOf(proto)) {
    if(proto === Foo.prototype)
        return true;
}
return false;

Dans ECMAScript3, il n'y a pas de moyen de définir un paramètre constructor qui se comporte de la même manière que la propriété intégrée, car les propriétés définies par l'utilisateur sont toujours énumérables (c'est-à-dire qu'elles sont visibles par les utilisateurs). for..in ).

Cela a changé avec ECMAScript5. Cependant, même si vous définissez constructor manuellement, votre code a toujours des problèmes : En particulier, c'est une mauvaise idée de définir prototype à une instance de la "classe" parent - le constructeur du parent ne doit pas être appelé lorsque la "classe" enfant est définie, mais plutôt lorsque les instances enfants sont créées.

Voici un exemple de code ECMAScript5 qui montre comment procéder :

function Pet(name) {
    this.name = name;
}

Pet.prototype.feed = function(food) {
    return this.name + ' ate ' + food + '.';
};

function Cat(name) {
    Pet.call(this, name);
}

Cat.prototype = Object.create(Pet.prototype, {
    constructor : {
        value : Cat,
        writable : true,
        enumerable : false,
        configurable : true
    }
});

Cat.prototype.caress = function() {
    return this.name + ' purrs.';
};

Si vous êtes coincé avec ECMAScript3, vous devrez utiliser un fichier personnalisé clone() fonction au lieu de Object.create() et ne sera pas en mesure de faire constructor non dénombrable :

Cat.prototype = clone(Pet.prototype);
Cat.prototype.constructor = Cat;

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