Почему невозможно изменить функцию конструктора из прототипа?

У меня есть такой пример.

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();

Я хочу изменить код в Rabbit() таким образом var jumps становится публичным. Я делаю это так:

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

Почему невозможно изменить код в функции конструктора таким образом?

4 ответа

Решение

Вы не можете изменить конструктор, переназначив prototype.constructor

Что происходит, так это Rabbit.prototype.constructor указатель на оригинальный конструктор (function Rabit(){...}), так что пользователи "класса" могут обнаружить конструктор из экземпляра. Поэтому при попытке сделать:

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

Вы будете влиять только на код, который опирается на prototype.constructor динамически создавать объекты из экземпляров.

Когда вы звоните new X двигатель JS не ссылается X.prototype.constructor, он использует X в качестве функции конструктора и X.prototype как прототип вновь созданного объекта., игнорируя X.prototype.constructor,

Хороший способ объяснить это - реализовать new оператор себя. (Крокфорд будет счастлив, не больше нового;)

// `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
});

Наследование в JS

Библиотеки, которые помогают с наследованием JS, реализуют наследование и полагаются на prototype.constructor с чем-то в духе следующее:

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;
}

Вы можете видеть, что в приведенном выше коде нам нужно исправить свойство конструктора, потому что оно иногда используется для создания экземпляра объекта, когда у вас есть только экземпляр. но это не влияет на фактический конструктор. Смотрите мой пост о наследовании JS http://js-bits.blogspot.com/2010/08/javascript-inheritance-done-right.html

Как переопределить конструктор Если вы действительно хотите переопределить конструктор, просто сделайте

// 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;

Попробуй это:

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

var rabbit2 = new Rabbit();             // create new object with new constructor
alert(rabbit2.jumps); 

Вместо того, чтобы устанавливать конструктор, вы хотите установить прототип Object как новую функцию, таким образом, когда объект создается, он выполняет прототип, устанавливая this.jumps нет, переопределяя значение, установленное предыдущим конструктором.

Вот мой jsfiddle: http://jsfiddle.net/ZtNSV/9/

Это прекрасный обходной путь создания объекта из литерала, а не из функции конструктора.

Во-первых, если вы хотите jumps член, который должен содержаться в объекте, а не просто локальная переменная в конструкторе, тогда вам нужно this ключевое слово.

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

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

И теперь вы можете легко получить доступ jumps публично так, как вы хотели:

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

Но, тем не менее, если вы создадите еще один объект Rabbit, у него изначально будет "да", как определено в конструкторе, верно?

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

То, что вы могли бы сделать, это создать Кролика из некоторого объекта Кролика по умолчанию. Конкретные кролики всегда будут иметь значение по умолчанию из объекта Rabbit по умолчанию, даже если вы измените его на лету, если только вы не изменили значение в конкретном объекте Rabbit (реализация). Это решение отличается от решения @Juan Mendes, которое, вероятно, является лучшим, но оно может открыть другую точку зрения.

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

Попробуйте следующее

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

var rabbit = new Rabbit();
alert(rabbit.jumps);  // Prints "no"
Другие вопросы по тегам