Почему невозможно изменить функцию конструктора из прототипа? - PullRequest
47 голосов
/ 13 февраля 2012

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

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

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

Ответы [ 3 ]

74 голосов
/ 14 февраля 2012

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

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

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;

Обратите внимание, что это не повлияет на код, который уже скопировал эту ссылку, например:

const myRefRabbit = Rabbit
0 голосов
/ 20 апреля 2016

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

Во-первых, если вы хотите, чтобы 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
0 голосов
/ 14 февраля 2012

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

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

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