web-dev-qa-db-fra.com

Pourquoi est-il impossible de changer la fonction constructeur du prototype?

J'ai un tel exemple.

function Rabbit() {
    var jumps = "yes";
};
var rabbit = new Rabbit();
alert(rabbit.jumps);                    // undefined
alert(Rabbit.prototype.constructor);    // outputs exactly the code of the function Rabbit();

Je veux changer le code dans Rabbit() pour que le var jumps Devienne public. Je le fais de cette façon:

Rabbit.prototype.constructor = function Rabbit() {
    this.jumps = "no";
};
alert(Rabbit.prototype.constructor);    // again outputs the code of function Rabbit() and with new this.jumps = "no";
var rabbit2 = new Rabbit();             // create new object with new constructor
alert(rabbit2.jumps);                   // but still outputs undefined

Pourquoi n'est-il pas possible de changer le code dans la fonction constructeur de cette façon?

49
Green

Vous ne pouvez pas changer un constructeur en le réaffectant à prototype.constructor

Ce qui se passe, c'est que Rabbit.prototype.constructor Est un pointeur vers le constructeur d'origine (function Rabbit(){...}), afin que les utilisateurs de la 'classe' puissent détecter le constructeur à partir d'une instance. Par conséquent, lorsque vous essayez de faire:

Rabbit.prototype.constructor = function Rabbit() {
    this.jumps = "no";
};

Vous n'affecterez que le code qui repose sur prototype.constructor Pour instancier dynamiquement des objets à partir d'instances.

Lorsque vous appelez new X, Le moteur JS ne fait pas référence à X.prototype.constructor, Il utilise X comme fonction constructeur et X.prototype Comme prototype de l'objet nouvellement créé ., ignorant X.prototype.constructor.

Un bon moyen d'expliquer cela est d'implémenter nous-mêmes l'opérateur new. (Crockford sera heureux, pas de nouveau;)

// `new` emulator
// 
// Doesn't reference `.constructor` to show that prototype.constructor is not used
// when istantiating objects a la `new`
function make(ctorFun, argsArray) {
  // New instance attached to the prototype but the constructor
  // hasn't been called on it.
  const newInstance = Object.create(ctorFun.prototype);
  ctorFun.apply(newInstance, argsArray);
  return newInstance;
}

// If you create a utility function to create from instance, then it uses the
// inherited `constructor` property and your change would affect that.
function makeFromInstance(instance, argsArray) {
  return make(instance.constructor, argsArray);
}

function X(jumps) {
  this.jumps = jumps;
}

// Flip the constructor, see what it affects
X.prototype.constructor = function(jumps) {
  this.jumps = !jumps;
}

const xFromConstructorIsGood = make(X, [true]);
const xFromInstanceIsBad = makeFromInstance(xFromConstructorIsGood, [true]);

console.log({
  xFromConstructorIsGood,
  xFromInstanceIsBad
});

Héritage dans JS

Les bibliothèques qui aident à l'héritage JS implémentent l'héritage et s'appuient sur prototype.constructor Avec quelque chose dans l'esprit:

function extend(base, sub) {

  function surrogateCtor() {}
  // Copy the prototype from the base to setup inheritance
  surrogateCtor.prototype = base.prototype;
  sub.prototype = new surrogateCtor();
  // The constructor property is set to the base constructor
  // with the above trick, let's fix it
  sub.prototype.constructor = sub;
}

Vous pouvez voir que dans le code ci-dessus, nous devons fixer la propriété constructeur car elle est parfois utilisée pour créer une instance d'un objet lorsque vous n'avez qu'une instance. mais cela n'affecte pas le constructeur réel. Voir mon article sur l'héritage JS http://js-bits.blogspot.com/2010/08/javascript-inheritance-done-right.html

Comment redéfinir un constructeur Si vous voulez vraiment redéfinir un constructeur, faites simplement

// If Rabbit had any custom properties on it 
// (or static properties as some call it), they would not be copied, you'd have to do that manually using getOwnPropertyNames

// See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyNames
var oldProto = Rabbit.prototype;
Rabbit = function() {...};
Rabbit.prototype = oldProto;

Notez que cela n'affecterait pas le code qui avait déjà copié cette référence, par exemple:

const myRefRabbit = Rabbit
77
Juan Mendes

C'est une solution de contournement merveilleuse créant un objet à partir d'un littéral, pas à partir d'une fonction constructeur.

Premièrement, si vous voulez que le membre jumps soit contenu dans l'objet, plutôt que d'être juste une variable locale dans le constructeur, alors vous avez besoin du mot clé this.

function Rabbit() {
    this.jumps = "yes";
};

var rabbit = new Rabbit();
alert(rabbit.jumps);                    // not undefined anymore

Et maintenant, vous pouvez facilement accéder à jumps publiquement comme vous le vouliez:

rabbit.jumps = 'no';
alert(rabbit.jumps);                    // outputs 'no' 

Mais toujours si vous créez un autre objet Rabbit, il aura initialement "oui" comme défini dans le constructeur, non?

var rabbit2 = new Rabbit();
alert(rabbit.jumps);                     // outputs 'no' from before
alert(rabbit2.jumps);                    // outputs 'yes'

Ce que vous pourriez faire est de créer un lapin à partir d'un objet Rabbit par défaut. Les lapins en béton auront toujours la valeur par défaut de l'objet Rabbit par défaut même lorsque vous le modifiez à la volée, sauf si vous avez modifié la valeur dans l'objet lapin en béton (implémentation). C'est une solution différente de celle de @Juan Mendes qui est probablement la meilleure mais elle peut ouvrir un autre point de vue.

Rabbit = {jumps : 'yes'};    // default object

rabbit = Object.create(Rabbit);
Rabbit.jumps = 'no';
rabbit2 = Object.create(Rabbit);

console.log(rabbit.jumps);   // outputs "no" - from default object
console.log(rabbit2.jumps);  // outputs "no" - from default object

// but...
rabbit.jumps = 'yes';
Rabbit.jumps = 'unknown';

console.log(rabbit.jumps);   // outputs "yes" - from concrete object
console.log(rabbit2.jumps);  // outputs "unknown" - from default object
0
Hrvoje Golcic

Essayez ce qui suit

function Rabbit() {
  this.jumps = "no";
};

var rabbit = new Rabbit();
alert(rabbit.jumps);  // Prints "no"
0
JaredPar