Подтвердить что ты не робот

Javascript Prototypal Inheritance Doubt II

Я занимаюсь некоторым наследованием в js, чтобы лучше понять его, и нашел что-то, что меня смущает.

Я знаю, что когда вы вызываете "конструкторскую функцию" с новым ключевым словом, вы получаете новый объект со ссылкой на прототип этой функции.

Я также знаю, что для того, чтобы сделать прототипное наследование, вы должны заменить прототип функции конструктора экземпляром объекта, который вы хотите быть "суперклассом".

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

function Animal(){}
function Dog(){}

Animal.prototype.run = function(){alert("running...")};

Dog.prototype = new Animal(); 
Dog.prototype.bark = function(){alert("arf!")};

var fido = new Dog();
fido.bark() //ok
fido.run() //ok

console.log(Dog.prototype) // its an 'Object' 
console.log(fido.prototype) // UNDEFINED
console.log(fido.constructor.prototype == Dog.prototype) //this is true

function KillerDog(){};
KillerDog.prototype.deathBite = function(){alert("AAARFFF! *bite*")}

fido.prototype = new KillerDog();

console.log(fido.prototype) // no longer UNDEFINED
fido.deathBite(); // but this doesn't work!

(Это было сделано в консоли Firebug)

1) Почему, если все новые объекты содержат ссылку на прототип функции creator, fido.prototype - undefined?

2) Является ли цепочка наследования [obj] → [constructor] → [prototype] вместо [obj] → [prototype]?

3) является свойством 'prototype' нашего объекта (fido) когда-либо проверяемого? если так... почему "deathBite" undefined (в последней части)?

Спасибо!

4b9b3361

Ответ 1

1) Почему, если все новые объекты содержат ссылка на функцию создателя прототип, fido.prototype undefined?

Все новые объекты содержат ссылку на прототип, который присутствовал на их конструкторе во время построения. Однако имя свойства, используемое для хранения этой ссылки, не prototype, как и в самой конструкторской функции. Некоторые реализации Javascript позволяют получить доступ к этому "скрытому" свойству с помощью некоторого имени свойства, такого как __proto__, где другие нет (например, Microsoft).

2) Является ли цепочка наследования [obj] → [конструктор] → [прототип] вместо [obj] → [prototype]?

Нет. Взгляните на это: -

function Base() {}
Base.prototype.doThis = function() { alert("First"); }

function Base2() {}
Base2.prototype.doThis = function() { alert("Second"); }

function Derived() {}
Derived.prototype = new Base()

var x = new Derived()

Derived.prototype = new Base2()

x.doThis();

Это предупреждение "Сначала" не второе. Если цепочка наследования прошла через конструктор, мы увидели бы "Второе". Когда объект сконструирован, текущая ссылка, содержащаяся в свойстве prototype-функции, переносится на скрытую ссылку объекта на ее прототип.

3) является свойством 'prototype' нашего объект (fido) когда-либо проверялся? если так... почему "deathBite" undefined (в последняя часть)?

Присвоение объекту (кроме функции) свойство с именем prototype не имеет особого значения, как указано ранее, объект не поддерживает ссылку на свой прототип через такое имя свойства.

Ответ 2

Невозможно изменить прототип объекта после его создания с помощью new.

В приведенном выше примере строки типа

fido.prototype = new KillerDog();

просто создает новый атрибут с именем prototype объекта fido и устанавливает этот атрибут в новый объект KillerDog. Это не отличается от

fido.foo = new KillerDog();

Как ваш код стоит...

// Doesn't work because objects can't be changed via their constructors
fido.deathBite();

// Does work, because objects can be changed dynamically, 
// and Javascript won't complain when you use prototype 
//as an object attribute name
fido.prototype.deathBite();

Специальное поведение prototype применяется только к конструкторам в javascript, где конструкторы function, которые будут вызываться с помощью new.

Ответ 3

Отвечайте номера на свои вопросы:

  • Свойство прототипа объекта не называется prototype. В стандарте используется [[prototype]] для его обозначения. Firefox делает это свойство общедоступным под именем __proto __.
  • Цепочка наследования [obj][prototype object]. Ваше исходное предположение ([obj][constructor][prototype]) неверно, и вы можете легко опровергнуть его, изменив constructor и/или constructor.prototype и проверив, какие методы можно вызвать на вашем [obj] — вы обнаружите, что эти изменения ничего не меняют.
  • prototype свойство объектов не проверяется и не используется. Вы можете настроить его так, как вам нравится. JavaScript использует его на объектах функции только во время построения объекта.

Для демонстрации № 3 здесь приведен код из Dojo:

dojo.delegate = dojo._delegate = (function(){
  // boodman/crockford delegation w/ cornford optimization
  function TMP(){}
  return function(obj, props){
    TMP.prototype = obj;
    var tmp = new TMP();
    if(props){
      dojo._mixin(tmp, props);
    }
    return tmp; // Object
  }
})();

Как вы можете видеть, он использует тот факт, что prototype используется только в одном месте, повторно используя ту же функцию TMP для всех делегированных объектов с разными прототипами. Фактически prototype назначается непосредственно перед вызовом функции с помощью new, и она будет изменена после этого, не затрагивая любые созданные объекты.

Вы можете найти созданную объект в моем ответе Связь между [[Prototype]] и прототипом в JavaScript.

Ответ 4

Я знаю, что уже ответили, но есть лучший способ сделать наследование. Вызов конструктора только с целью наследования нежелателен. Один из нежелательных эффектов.

function Base() {this.a = "A"}
function Child() {this.b = "B"};

Child.prototype = new Base();

Теперь вы добавили свойство "a" к прототипу Child, которого вы не намеревались.

Здесь правильный путь (я этого не изобретал, Ext-JS и другие библиотеки используют это)

// This is used to avoid calling a base class constructor just to setup inheritance.
function SurrogateCtor() {}

/**
 * Sets a contructor to inherit from another constructor
 */
function extend(BaseCtor, DerivedCtor) {
  // Copy the prototype to the surrogate constructor
  SurrogateCtor.prototype = BaseCtor.prototype;
  // this sets up the inheritance chain
  DerivedCtor.prototype = new SurrogateCtor();
  // Fix the constructor property, otherwise it would point to the BaseCtor
  DerivedCtor.prototype.constructor = DerivedCtor;
  // Might as well add a property to the constructor to 
  // allow for simpler calling of base class method
  DerivedCtor.superclass = BaseCtor;
}

function Base() {
  this.a = "A";
}

Base.prototype.getA = function() {return this.a}

function Derived() {
  Derived.superclass.call(this);  // No need to reference the base class by name
  this.b = "B";
}

extend(Base, Derived);
// Have to set methods on the prototype after the call to extend
// otherwise the prototype is overridden;
Derived.prototype.getB = function(){return this.b};
var obj = new Derived();

Еще проще: добавить третий параметр для расширения, где вы указываете метод производного класса, чтобы вам не приходилось вызывать расширение, а затем добавлять методы к прототипу

extend(BaseCtor, DerivedCtor, {
  getB: function() {return this.b}
});

Тогда есть много других вещей, которые вы могли бы сделать для синтаксического сахара.

Сообщено об этом: http://js-bits.blogspot.com/2010/08/javascript-inheritance-done-right.html

Ответ 5

Стоит отметить, что в ECMAScript 5 (т.е. последней версии языка JavaScript) вы можете получить доступ к внутреннему свойству [[Prototype]] экземпляра через Object.getPrototypeOf:

Object.getPrototypeOf(fido) === Dog.prototype