Прототипы
В наших предыдущих примерах классов у нас было, как правило, три возможности для назначения свойств. Мы могли прицепить их прямо к объекту класса, например A.temp. Или же могли установить временные свойства внутри блока класса {}, используя выражение var x = 5. Мы также могли назначать свойства для объекта (this), "проходящего" сквозь объект активации класса, во время создания экземпляра. Такие свойства затем отображались в новом экземпляре. Казалось бы, вариантов для назначения свойств достаточно много, но всё же кое-что до сих пор оставалось для нас невозможным. Нет никакого способа назначать свойства, принадлежащие классу, но в то же время доступные для экземпляра. Наверное, это звучит странно, так что давайте рассмотрим такую ситуацию подробнее. Вот один простой пример, так сказать "собачий" (Dog):
Dog = function() { this.legs = 4; } rover = new Dog(); fido = new Dog(); yeller = new Dog();
Сначала может показаться, что свойство this.legs принадлежит к классу Dog из-за того, что оно назначается там же. Можно подумать, что его специально "оставили доступным" для каждого экземпляра Dog и что это свойство, вместе с тем, является частью класса. Однако это не так. Свойство legs сказывается только на объекте экземпляра и нет никакой возможности добраться до него, если вы начнёте с Dog (его не находят даже такие выражения: Dog.instance.legs, Dog.benji.legs, Dog.legs). Определённо, это не свойство Dog. Это свойство экземпляра (benji.legs, fido.legs... - вот так пойдёт). А что если мы попробуем так: Dog.legs = 4 ? Может тогда это свойство станет свойством класса доступным для экземпляров? Если немного "поколдовать", экземпляры будут (иногда) иметь доступ к такому свойству (benji.constructor.legs - уж если вам так интересно), но правильно ли это? Должен ли benji "спрашивать" класс, определяющий Dog, сколько у него должно быть legs или он и так будет знать об этом? Если предположить, что benji - это тип (разновидность) собаки (Dog), то он (benji) должен иметь все собачьи (Dog) достоинства, которые должны распространяться на него автоматически. Если мы напишем Dog.legs = 4, то benji.legs останется равным 0. Нам поможет то, что называется объект-прототип.
Все классы имеют объект-прототип, который "виден" экземплярам. Он как бы подвешен к классу и содержит родственные ему свойства. Интересно, что экземпляры класса автоматически "заглядывают" туда за свойствами. Разумеется, сначала экземпляры проверяют сами себя, ведь зачем спрашивать друга "сколько времени?", если у самого на руке часы! Экземпляр проверяет себя на наличие свойства и если таковое не найдено, то он "рассуждает" следующим образом: "Я - разновидность Собаки (в нашем примере - Dog) и мне интересно, есть ли у Собаки (Dog) это свойство". После этого он "идёт и берёт" свойство у прототипа своего класса (Говорящая собака - это круто!).
Теперь можно поместить в собачий (Dog) класс информацию обо всех собаках и использовать экземпляры для информации, которая уникальна для каждой собаки. Осталось только выучить синтаксис, что обычно не представляет проблем:
Dog = function( ){} Dog.prototype.legs = 4;
rover = new Dog( ); fido = new Dog( ); yeller = new Dog( );
fido.puffyHair = true;
Теперь всё на своём месте - у всех Собак (Dog) по четыре лапы, но вот puffyHair - личный выбор fido. Однако, это не означает, что у остальных собак не может быть точно такой же "причёски". Просто данное свойство (puffyHair) специфично и индивидуально, оно не является общей особенностью собак вообще. Итак, у fido есть свойство puffyHair, у других его нет. У всех у них есть свойство legs, хотя оно пошло скорее от Dog.prototype.legs, чем от fido.legs. Каким образом у fido есть доступ к legs? Вот в чём вся прелесть! Обращение к такому свойству происходит так же, как и любое другое обращение к любому другому свойству fido - fido.legs. И вообще, мы не знаем, не хотим знать, да нам и не нужно знать, откуда берётся это свойство, что, разумеется, не означает, что нам не надо поставить и хранить его в специально отведённом для него месте во время работы нашей программы. Но обращаетесь вы к нему так, как будто это свойство экземпляра: rover, fido или yeller. Да, это замечательно, но возникает путаница с терминологией, поскольку мы уже привыкли называть объекты контейнерами и даже коробками. Давайте же немного проясним ситуацию. Мы можем представлять себе объекты, как коробки со списком их содержимого, прилепленным клейкой лентой к внешней стенке. Однако, происходящее внутри этих объектов вовсе не похоже на то, что может случиться с обычной картонной коробкой. Для этого нам потребуется новая метафора.
Наследование работает подобно многослойному оконному стеклу. Если вы бывали когда-нибудь на севере, то могли видеть окна с тройными стёклами. Вот то, что нам нужно. Они обладают такой прекрасной изоляцией, что дольше сохраняют тепло в вашем доме ("Тепло" значит "хорошо" - это для тех, кто в Фениксе, ведь вы, ребята, должно быть, пользуетесь тройными стёклами, чтобы уберечься от жары). Мы заговорили об этих окнах потому, что они имеют одно очень интересное для нас свойство. Если вы просто посмотрите на них, вы не узнаете, сколько в них слоёв. Может кое-кому и достаточно взглянуть на счёт за отопление, чтобы узнать, сколько их там. Кстати, у строителей на этот случай есть свои методы. Они зажигают зажигалку перед стеклом и считают, сколько там отражений. (Отсюда правило: даже строителю нужна зажигалка). Итак, у нас три (или более) слоя стекла. Подсознательно мы уже понимаем, что стекло там не одно, но, сколько именно их там, мы не знаем. Последнее, что можно сделать, это попробовать просунуть клочки бумаги между стёклами (только не делайте так дома, а то испортите окно и нарушите его герметичность!).
Вы можете просунуть какие-нибудь предметы между разными слоями стекла, но на вид будет казаться, что все они остались на одном слое, вот что самое интересное! Если разместить кусок бумаги на одном слое в то же место, где на другом слое находится такой же кусок бумаги, в поле зрения у вас останется только кусок ближайшего к вам слоя. Ближайший к вам слой стекла представляет для нас наибольший интерес, так как до него проще добраться, чем до остальных. Вы можете изменять куски бумаги на переднем слое (modify) или добавлять новые (add) или даже изымать их и выбрасывать (delete). Другие слои, как правило, остаются неизменными с тех пор, как вы их создали, если только у вас нет крайней необходимости изменить их (можно прибегнуть к расслоению, но вы должны быть предельно осторожными!). Обычно вы делаете изменения, помещая куски бумаги на передний слой стекла (тот, что закрывает другие), а также изменяя этот кусок. Ни в коем случае нельзя рисовать на стекле - вам, наверное, об этом говорили, когда вам было года четыре или около того. Казалось бы, информации для запоминания слишком много. Но не унывайте! В ActionScript встроена автоматическая проверка!
Посмотрите на изображение стёкол выше. Видите, как расположены квадратики каждого слоя? Жёлтые на первом слое, оранжевые - на втором, фиолетовые - на третьем.
- Вопрос:
Что бы вы делали, если бы теперь вам нужно было изменить центральный (фиолетовый) квадрат?
- Ответ:
Налепил бы новый бумажный квадратик сверху - а то слишком уж муторно расслаивать стёкла.
Таким образом, свойства объекта менее похожи на схематичные рисунки мелом на доске, а скорее на кусочки бумаги, приклеенные к многослойному оконному стеклу. Первый (передний) слой стекла - это наш экземпляр. Когда он создаётся, то помещает свойства на своём уровне. Свойства прикреплены к нему (или, точнее, к безымянному объекту, которым он станет) посредством использования ключевого слова this После того, как он создан, свойства также могут быть прикреплены вручную. Для этого можно использовать выражение instance.x=5. Далее мы рассмотрим ещё несколько способов появления свойств на слое экземпляра. Второй слой стекла содержит свойства, принадлежащие создавшему экземпляр классу. Они находятся в выражении Class.prototype. Что бы вы ни добавляли ко второму слою, всё это будет "видно" для всех экземпляров, созданных с помощью класса. Возвращаясь к экземпляру с собакой (Dog), если бы мы добавили свойство puffyHair к Dog.prototype (второй слой стекла), то все три собаки имели бы свойство puffyHair. Старый крикун (yeller), каким бы не был преданным и верным, вряд ли бы остался доволен.
Есть кое-что в нашей новой "оконной" модели, что требует отдельного объяснения. А именно: свойство, добавляемое к Class.prototype появляется во всех экземплярах класса. Означает ли это, что все экземпляры получают "копию" этого слоя стекла? Нет. Слой стекла один и тот же для всех экземпляров. Возможно, вам больше понравится представлять себе слои стекла экземпляров, находящиеся под углом ко второму слою? Тогда, по мере перемещения от экземпляра к экземпляру, всё само собой "выравнивается" и встаёт на свои места. Аллегория с многослойными оконными стёклами хорошо подходит для описания одного экземпляра, но не всей системы наследования. В случае с наследованием, такая метафора нас не устраивает потому, что наследование, это скорее подсознательная, чем визуально-осознанная система классификаций. Нетрудно понять, что все млекопитающие подпадают под одно и то же определение и если это определение изменится, то изменятся и определения всех млекопитающих таким образом, чтобы получить новую информацию. Такое тасование информации невозможно в случае с реальными объектами, которые физически связаны друг с другом. Разумеется, это не означает, что модель становится ненужной, ведь она прекрасно работает с описанием отдельного экземпляра и замечательно объясняет старшинство, но об этом в следующем разделе.
<<
ООП во Flash 5 >>