Javascript Prototypal Наследование?
Я делал некоторые наследования в 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 ответов
1) Почему, если все новые объекты содержат ссылку на прототип функции-создателя, fido.prototype не определен?
Все новые объекты содержат ссылку на прототип, который присутствовал в их конструкторе во время создания. Однако имя свойства, использованное для хранения этой ссылки, не является prototype
как это на самой функции конструктора. Некоторые реализации Javascript разрешают доступ к этому "скрытому" свойству через какое-то имя свойства, например __proto__
где другие нет (например, Microsoft).
2) Является ли цепочка наследования [obj] -> [constructor] -> [prototype] вместо [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();
Это оповещение "Первый", а не Второй. Если бы цепочка наследования проходила через конструктор, мы бы увидели "Второе". Когда объект создается, текущая ссылка, хранящаяся в свойстве прототипа функции, передается скрытой ссылке на объект в его прототипе.
3) проверено ли когда-либо свойство 'prototype' нашего объекта (fido)? если так... почему "смертный укус" не определен (в последней части)?
Присвоение объекту (кроме функции) свойства с именем prototype
не имеет особого значения, как указывалось ранее, объект не поддерживает ссылку на свой прототип через такое имя свойства.
Вы не можете изменить прототип объекта, если он был создан с помощью 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
,
Ответьте числами на свои вопросы:
- Свойство прототипа объекта не вызывается
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.
Я знаю, что на него уже ответили, но есть лучший способ наследования. Вызов конструктора только для наследования нежелателен. Одним из нежелательных эффектов является.
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-inheritance-done-right.html
Стоит отметить, что в ECMAScript 5 (то есть в последней версии языка JavaScript) вы можете получить доступ к внутреннему свойству [[Prototype]] экземпляра через Object.getPrototypeOf
:
Object.getPrototypeOf(fido) === Dog.prototype