Наследование во Flash MX
Мы уже познакомились с процессом создания объектов во Флэш МХ и теперь знаем, что любой объект легко поддается модификации. Но ведь прототипом объекта всегда тоже является какой-то объект! Значит и прототип можно модифицировать. К чему это приводит, мы сейчас увидим.
Наследование как модификация прототипа
В предыдущей лекции прототип объекта первоначально был у нас пустым, и каждый метод в него мы добавляли "вручную". Предположим однако, что мы написали
function MyClass(){} MyClass.prototype = new MyBaseClass();
и затем уже модифицируем имеющийся прототип. В результате многократного применения подобной операции возникает структура, подобная изображенной на рис.7.1.
Вертикальные стрелки символизируют создание нового объекта при помощи вызова оператора new.
Нетрудно убедиться в том, что у нас получилось самое настоящее наследование. Сейчас мы по пунктам проверим, что все условия, необходимые для наследования, в данном случае выполнены.
copy on write и цепочка поиска
Предположим, что мы написали obj.a, то есть запросили значение поля a у объекта obj. Где Флэш будет искать значение этого поля? Сначала, разумеется, он посмотрит среди полей, которые мы непосредственно завели в объекте obj. Если ничего не найдено, то, как мы знаем, Флэш принимается за поиски в прототипе, который он находит с помощью ссылки __proto__. Но поиски в прототипе идут по той же самой схеме, то есть если непосредственно в прототипе искомая ссылка не нашлась, то мы идем в прототип прототипа (относительно исходного объекта это __proto__.__proto__ ) и так далее вплоть до прототипа типа Object (который автоматически попадает в "корень" этой цепочки, даже если у самого базового класса прототип был вовсе не указан). Очевидно, что такой механизм полностью соответствует наследованию в обычном понимании этого слова. Более того, если в производном классе создана какая-то функция с тем же именем, что и в базовом, то при поиске по цепочке прототипов будет сначала найдена функция из производного класса (и на этом поиск прекратится). То есть таким образом реализовано переопределение виртуальных функций. Мы видим, что все функции являются виртуальными. Более того, виртуальными являются также и поля. (Временами случается пожалеть об отсутствии возможности делать виртуальные поля при работе на С++ или Java.)
Можно ли как-нибудь узнать, добавлено ли некоторое поле непосредственно в исследуемый объект или же оно найдено где-то в цепочке __proto__? Оказывается, можно, хотя и приходится для этого использовать недокументированную функцию Object.hasOwnProperty. Вот код, который демонстрирует все, что нам нужно.
prot = {a: 5, b: 6}; constr = function(){} constr.prototype = prot; obj = new constr(); obj.a = 7; trace("obj.a = " + obj.a); trace("obj.b = " + obj.b); trace("Has own property a ? " + obj.hasOwnProperty("a")); trace("Has own property b ? " + obj.hasOwnProperty("b")); obj.b = undefined; delete obj.a; trace("-------------"); trace("obj.a = " + obj.a); trace("obj.b = " + obj.b); trace("Has own property a ? " + obj.hasOwnProperty("a")); trace("Has own property b ? " + obj.hasOwnProperty("b"));
Вот что получается в результате:
obj.a = 7 obj.b = 6 Has own property a ? true Has own property b ? false ------------- obj.a = 5 obj.b = Has own property a ? false Has own property b ? true
Итак, что же произошло? Мы создали объект prot, который использовали в качестве прототипа объекта типа constr. Собственно говоря, тут еще не было наследования как такового, ибо prot - это единичный объект, не описывающий тип. Если бы мы непременно хотели устроить наследование, мы могли бы сделать этот объект прототипом некоего класса Base, а затем объект этого класса использовать в качестве прототипа класса constr. Впрочем, мы пока что иллюстрируем не наследование, а цепочку поиска. Поэтому мы нарочно подчеркнули в названии constr тот факт, что мы используем эту функцию в качестве конструктора, а то, что эта функция-объект задает класс, нам не очень важно. В конце концов, мы могли обойтись вовсе без конструктора, а написать obj.__proto__ = prot. Можете убедиться, что этот вариант тоже работает; более подробно мы его разберем, когда будем говорить об альтернативном наследовании (в предпоследнем параграфе этой лекции).
Так или иначе, мы получили объект со своими собственными полями и с полями, унаследованными из прототипа. Мы видим, что функция Object.hasOwnProperty работает так, как ожидалось. Заодно убеждаемся в том, что уничтожить поле и присвоить ему значение undefined - это разные вещи. В последнем случае значения из прототипа скрываются свежеприсвоенным значением undefined.