Javascript Prototypal Наследование? - PullRequest
16 голосов
/ 24 декабря 2008

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

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

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

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

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) Почему, если все новые объекты содержат ссылку на прототип функции-создателя, fido.prototype не определен?

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

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

Ответы [ 5 ]

12 голосов
/ 24 декабря 2008

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

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

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

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

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

Это предупреждение «Первый», а не Второй. Если бы цепочка наследования проходила через конструктор, мы бы увидели «Второе». При создании объекта текущая ссылка, хранящаяся в свойстве прототипа Functions, передается скрытой ссылке на объект в его прототипе.

3) это свойство «прототипа» нашего объект (фидо) когда-либо проверял? если так... почему «смертельный укус» не определен (в последняя часть)?

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

7 голосов
/ 24 декабря 2008

Вы не можете изменить прототип объекта, если он был создан с помощью 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 s, которые будут вызываться с new.

4 голосов
/ 24 декабря 2008

Ответьте числами на ваши вопросы:

  1. Свойство прототипа объекта не называется prototype. Стандарт использует [[prototype]] для его обозначения. Firefox делает это свойство общедоступным под именем __proto __.
  2. Цепочка наследования: [obj] & rArr; [prototype object]. Ваше первоначальное предположение ([obj] & rArr; [constructor] & rArr; [prototype]) неверно, и вы можете легко опровергнуть его, изменив constructor и / или constructor.prototype и проверив, какие методы можно вызвать на вашем [obj] & mdash; вы обнаружите, что эти модификации ничего не меняют.
  3. 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 .

2 голосов
/ 18 сентября 2011

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

Object.getPrototypeOf(fido) === Dog.prototype
2 голосов
/ 31 июля 2010

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

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's 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's 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-наследование-сделано-право.html

...