Сразу после того, как мы создали функцию, у нее есть свойство prototype, куда записан некоторый объект:
var Foo = function() {}
alert(Foo.prototype) // [object Object]
Правда, это свойство как тот суслик, который есть несмотря на то, что его не видишь:
var Foo = function() {}
Foo.prop = 'ololo';
for (var i in Foo) {
alert('Foo.'+i + ' = ' + Foo[i])
}
// выведет "Foo.prop = ololo"
// и НЕ выведет никакого Foo.prototype
Если попытаться изучить, как устроен объект, который сидит в этом свойстве, может показаться, что он - пустой:
var Foo = function() {}
for (var i in Foo.prototype) {
alert('Foo.prototype.'+i + ' = ' + Foo.prototype[i])
}
alert('КОНЕЦ!')
// покажет только "КОНЕЦ!"
На самом деле в нем тоже сидит невидимый суслик, в виде свойства Foo.prototype.constructor, куда записана ссылка на саму функцию:
function Foo() {}
alert(Foo.prototype.constructor === Foo) // true
Когда же может пригодиться свойство Foo.prototype? Тогда, когда Foo используется в качестве конструктора, то есть с оператором new. Конструктор создает объекты, все это знают. В JavaScript это тоже так, но логика создания слегка запутанная. Вот перед нами простой с виду код:
var Foo = function() {}
var a = new Foo;
alert(a)
Любой пыхарь с уверенностью скажет "Мы создали объект а, являющийся экземпляром класса Foo". На самом деле в 7 символах "new Foo" заключена следующая магия:
- Мы создаем пустой объект (как если бы написали a = {})
- Прототипом этого объекта назначается то, что сидит в Foo.prototype
- Этот объект привязывается к this в теле Foo
- Исполняется функция Foo
- Оператор new возвращает созданный объект, если Foo не вернула что-либо другое.
"Прототип" из пункта 2 - это такой объект, где будут искаться свойства созданного объекта a, которые он сам в явном виде не содержит. Код ниже иллюстрирует все это более развернуто:
var Foo = function() {
alert('я - фу!')
this.prop = 'lol'
}
// добавляем свойство к Foo.prototype
Foo.prototype.bar = 'trololo'
var a = new Foo // исполняется Foo, видим алерт "я - фу"
// в переменную a записывается созданный объект
// когда Foo исполнялась, this указывало на него
alert(a.prop) // например, через this-ссылку мы добавили свойство prop
alert(a.bar) // а свойство bar берется из прототипа
ОК, вроде разобрались, что такое "прототип объекта", откуда он берется и зачем нужен. А как его посмотреть? В идеальном мире мы могли бы обратиться к obj.prototype и получить прототип объекта. Но увы, как мы видели выше, свойство с таким названием используется иначе - хранит то, что функция-конструктор сделает прототипом объекта. Напрашивается вывод, что можно получить прототип через .prototype конструктора. Попробуем:
var Foo = function() {}
Foo.prototype.prop = 'ololo'
var a = new Foo()
alert(a.prop); // берем из прототипа неявно
alert(a.constructor.prototype.prop); // а теперь - явно!
Вроде бы работает. Но что, если мы пытались изобразить наследование, расширив Foo другим "классом"?
function Foo() {}
function Bar() {
this.prop = 'ololo'
}
Foo.prototype = new Bar // типа наследуемся
var a = new Foo
alert(a.prop) // работает
alert(a.constructor.prototype.prop) // undefined - ой :(
// а кстати, что у нас в a.constructor?
alert(a.constructor) // Bar!
Самое время задуматься, куда, собственно, указывает a.constructor. А, собственно, никуда не указывает, потому что у a нету такого свойства, оно берется из цепочки прототипов. В примере выше "прототип a" - это экземпляр Bar (потому что в Foo.prototype результат выполнения new Bar). У экземпляра Bar тоже нету свойства constructor. Зато оно есть у прототипа экземпляра Bar - потому что Bar.prototype (прототип экземпляра Bar) имеет свойство .constructor, указывающее на Bar ("свойство-суслик", о котором я писал в самом начале).
Если подумать, факап в этом примере случился из-за того, что мы "расширили" "класс" "экземпляром" (да, все - в кавычках!). Логичнее "расширять" "класс" "классом", но сути это не меняет - на obj.constructor.prototype в поисках прототипа мы полагаться не можем. Потому что obj.constructor - это не "функция, создавшая obj", а в лучшем случае - "функция, создавшая прототип obj" (так сформулировано в доках на MDN). В лучшем случае - потому что, вообще-то, там может быть что угодно:
// "Класс", создающий компанию с ключевыми сотрудниками
function Company (team) {
for (var profession in team) {
this[profession] = team[profession]
}
}
var AvtoVAZ = new Company({
president:'Игорь Комаров',
constructor:'Сергей Кудрюк' // главный конструктор АвтоВАЗа
})
alert(AvtoVAZ.constructor)
// упаси Бог пытаться создать новую компанию так:
// var KAMAZ = new AvtoVAZ.constructor()
В общем, догадаться самостоятельно не получается, надо лезть в доки, маны и прочую матчасть. Там мы обнаруживаем совершенно курьезную ситуацию: JavaScript, прототипно-ориентированный язык, обзавелся нормальным способом получения прототипа только на 14-м году своей жизни, в версии 1.8.1! Этот способ называется Object.getPrototypeOf() и не поддерживается IE младше 9.
function Foo() {}
function Bar() {
this.prop = 'boo'
}
var b = new Bar
Foo.prototype = b
var a = new Foo
// получаем прототип
var aproto = Object.getPrototypeOf(a)
alert(aproto.prop) // boo из экземпляра Bar
// меняем в прототипе
aproto.prop = 'woo'
alert(a.prop) // woo - "меняется" и в экземпляре
// меняем в прототипе неявно
b.prop = 'zoo'
alert(a.prop) // zoo - работает!
// потому что Foo.prototype, aproto и b - все указывают на один и тот же объект
До появления этой штуки у разработчиков было только свойство .__proto__, которое работает только в Mozilla-образных браузерах.
function Foo() {}
var proto = {prop:'woo'}
Foo.prototype = proto
var a = new Foo
alert(a.__proto__.prop) // свойство есть в прототипе
proto.prop = 'zoo'
alert(a.__proto__.prop) // поменяли прототип, отразилось на экземпляре
a.__proto__.prop = 'boo'
alert(proto.prop) // и в обратную сторону работает
Настало время для типа-саммари.
Что такое "прототип объекта obj"?
Это объект, к которому JS обращается за свойствами, которых нету у самого obj.
Что такое obj.prototype?
Если obj - это функция, то obj.prototype - то, что станет прототипом объекта при вызове new obj().
Что такое obj.constructor?
Если очень повезет - ссылка на функцию, создавшую obj, или прототип obj. А так вообще - что угодно :)
Как кроссбраузерно получить ссылку на прототип объекта?
Никак :) благо не часто нужно.
Что такое obj.__proto__?
Это прототип объекта obj, если у вас мозилла
Что такое Object.getPrototypeOf(obj)
Это способ получить ссылку на прототип объекта obj, который станет кроссбраузерным после смерти IE8 (ну FF до 3.5)
Зачем мне все это знать? о_0
Если создание сайтов - ваша работа, то... вам это все знать нафиг не нужно :) Если только для общей образованности, ну и чтоб голову поломать на досуге.