учебники, программирование, основы, введение в,

 

Наследование во Flash MX

Мы уже познакомились с процессом создания объектов во Флэш МХ и теперь знаем, что любой объект

легко поддается модификации. Но ведь прототипом объекта всегда тоже является какой-то объект! Значит

и прототип можно модифицировать. К чему это приводит, мы сейчас увидим.
Наследование как модификация прототипа
В предыдущей лекции прототип объекта первоначально был у нас пустым, и каждый метод в него мы добавляли

"вручную". Предположим однако, что мы написали
function MyClass(){}
MyClass.prototype = new MyBaseClass();

и затем уже модифицируем имеющийся прототип. В результате многократного применения подобной операции

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


Работа с прототипами различных уровней
Итак, мы видим, что имея объект, можно обратиться как к его собственным полям, так и к полям прототипа, полям

прототипа прототипа и т.д. Причем, такое обращение может осуществляться как автоматически (при поиске поля

или функции), так и вручную при помощи цепочки ссылок вида __proto__.__proto__.__proto__ и т.д. Похоже, мы

злоупотребляем здесь выражением "и так далее", но лишь по той причине, что это наилучший способ выражать

мысли при разговоре о цепочке прототипов произвольной длины.
Так вот, возникает очевидное желание, во-первых, научиться выводить на печать список полей объектов с

указанием того, из какого именно прототипа (или же из самого объекта) взято каждое поле. Во-вторых, поскольку

в процессе выполнения нашего первого желания не обойтись без использования оператора for...in, нужно разобраться,

как ведет себя этот оператор по отношению к полям объекта и его прототипов различного уровня.
Как работает for...in, если есть базовые классы
Прежде всего, еще раз напомним одну особенность работы оператора for...in, о которой мы уже писали во второй

лекции. А именно, поля, созданные позже, встречаются при переборе раньше (за исключением созданных в

фигурных скобках в процессе инициализации объекта). Логично, что и естественный порядок перебора прототипов

(то есть сначала находим все свойства в самом объекте, потом ищем в прототипе, потом в прототипе прототипа

и так далее) меняется на противоположный. А, возможно, в начале работы оператора for...in Флэш производит

перебор полей в прямом порядке, складывает все имена в какой-нибудь буфер и лишь потом начинает выполнять

тело цикла, вынимая имена из буфера в обратном порядке. Удивительно, что в онлайн-документации ни слова

про этот обратный порядок не сказано. Но, как бы то ни было, мы сейчас продемонстрируем все вышеописанные

свойства оператора for...in на простом примере. Запускаем следующий код:
obj1 = {a: 1, b: 2};
obj2 = {b: 3, c: 4};
obj3 = {c: 5, d: 6};
// Вместо нормального наследование просто
// определяем __proto__ - в данном случае
// это ничего не меняет
obj3.__proto__ = obj2;
obj2.__proto__ = obj1;

for(name in obj3){
trace("obj3." + name + " = " + obj3[name]);
}

и получаем в результате:
obj3.a = 1
obj3.b = 3
obj3.c = 5
obj3.d = 6

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

цепочке __proto__ - считая от того объекта, у которого мы запрашиваем переменную. Можно сказать и

по-другому: переменные берутся из объекта "самого производного" класса, в котором они были переопределены.

Все же первый вариант лучше, ведь мы сейчас, фактически, не создавали никаких классов, а сделали только

несколько объектов, связанных цепочкой ссылок __proto__. Также мы видим и описанный выше "обратный

порядок перебора": до поля a дольше всего добираться по цепочке __proto__, а выводится оно первым.

(И алфавитный порядок здесь ни при чем; вы можете переименовать переменные и убедиться, что от их имен

порядок вывода не зависит.) Забавно, что инициализировать ссылки __proto__ для создания цепочки можно

прямо при создании объектов при помощи примерно такого кода:
obj1 = {a: 1, b: 2};
obj2 = {__proto__: obj1, b: 3, c: 4};
obj3 = {__proto__: obj2, c: 5, d: 6};
for(var name in obj3){
trace("obj3." + name + " = " + obj3[name]);
}

Этот код дает точно такой же результат, что и предыдущий.
Пример: копирование массива с помощью рекурсивного снятия защиты
В качестве более интересного примера использования for...in с учетом цепочки __proto__ мы рассмотрим прием копирования объектов (в том числе системных) с предварительным рекурсивным снятием защиты. Снятие защиты позволит нам скопировать практически все системные поля. В частности, сейчас мы увидим, что этот прием позволяет скопировать массив нестандартным способом. Это не значит, конечно, что новый способ чем-то лучше - наоборот, он хуже по многим соображениям. Но интересно то, что этот способ в принципе существует.
Итак, следующий код рекурсивно снимает защиту со всех полей объектов, имеющихся в цепочке __proto__ (не делайте так в ваших программах, если не уверены, что вам это нужно). И затем копирует все поля заданного объекта (и объектов в его цепочке __proto__) в другой объект.
// Эта функция снимает защиту со всех полей
// всех объектов в цепочке __proto__ аргумента.
_global.unhideAll = function(obj){
ASSetPropFlags(obj,null,0,1);
// На null можно не проверять, поскольку
// null == undefined, хотя и null !== undefined.
if (obj.__proto__ != undefined) unhideAll(obj.__proto__);
}
_global.copyObj = function(toObj, fromObj){
// снимаем защиту от for...in
unhideAll(fromObj);
// и затем копируем все, что видит for...in -
// то есть все, кроме переопределенных переменных
// или функций
for (var fieldName in fromObj){
toObj[fieldName] = fromObj[fieldName]; 
}
}
// Проверяем, удается ли скопировать таким образом массив.
someObj = {};
arr = [2, 6, 0];
copyObj(someObj, arr);
// Убеждаемся, что массив не только скопировался, но и
// работают его методы.
trace("Отсортированный массив = [" + someObj.sort() + "]");

На выходе получим
Отсортированный массив = [0,2,6]

Так что у нас действительно произошло полноценное копирование. Все же напомним, что скопировать

массив можно быстрее и удобнее при помощи метода slice, написав примерно следующее:

copy_array = source_array.slice(0, source_array.length);

Вывод полного содержимого объекта

В этом небольшом разделе мы поговорим о том, какие есть удобные способы вывести полную

таблицу полей и методов объекта - таблицу, в которой все переменные и функции рассортированы

по принадлежности к одному из прототипов в цепочке __proto__.
Для системных объектов вывести все их содержимое в правильном порядке довольно просто

Вот код, который сию замечательную методику реализует.

   // Печатаем имя и значение поля, но не смешиваем 
   // пустую строку и undefined
_global.printField = function(name, value){
   if (value == undefined) value = "undefined";
   trace(name + ": " + value)
}
   // Печатаем все, что доступно функции for...in
   // Чтобы увидеть скрытые поля, снимаем с них защиту
   // Если не предпринять никаких мер, эта функция
   // выведет как непосредственные поля и методы объекта,
   // так и открытое содержимое его прототипов всех уровней.
_global.printFieldsByForIn = function(obj, str, tempProto){
   trace(":::::::::::  " + str + "  ::::::::::::");
trace(":::::::::::::::::::::::::::::::::::::::::::::::::::::::::");
      // Снимаем "защиту" со скрытых полей.
   ASSetPropFlags(obj, null, 0, 1);
   for(var name in obj){
         // Принимаем меры для того, чтобы наши действия
         // с обнулением __proto__ не отражались на выводимой
         // информации.
      if (name == "__proto__") printField(name, tempProto);
      else printField(name, obj[name]);       
   }
   trace(":::::::::::::::::::::::::::::::::::::::::::::::::::::::::");
}
   // В этой рекурсивной функции мы, собственно, и реализуем 
   // вышеописанный фокус с "отцеплением цепочки" __proto__ .
   _global.printAllFields = function(obj, name){
   var tempProto = obj.__proto__;
   obj.__proto__ = null;
   printFieldsByForIn(obj, name, tempProto);
   obj.__proto__ = tempProto;
      // Проверка на null не нужна: null == undefined, хотя
      // отличить их и можно при помощи оператора ===.
   if (obj.__proto__ != undefined)
      printAllFields(obj.__proto__, name + ".__proto__");
}
// А эта функция просто вызывает основную рабочую функцию и 
// добавляет "элементы оформления" (в текстовом виде, разумеется).
_global.dumpObj = function(obj, name){
   trace("================================================");
   if (name == undefined) name = "<Dumped object>";
   printAllFields(obj, name);
   trace("================================================");
   trace("");
}
   // Теперь тестируем функцию dumpObj на разных объектах.
   // Сначала на простейшем объекте с одним полем.
myObject = new Object();
myObject.a = 10;
dumpObj(myObject, "myObject");
   // Затем тестируем на массиве.
someArray = [1, 2, 4, 7];
dumpObj(someArray, "someArray");
   // Далее тестируем на примитивной сроке.
a = "Примитивная строка";
dumpObj(a, "Примитивная строка");
   // И, наконец, на системном объекте _root, который
   // представляет из себя корневой MovieClip.

dumpObj(_root, "_root");

Выводит этот код очень большое количество текста, но текст этот того стоит - ведь это внутренности

разных системных объектов, что может быть интереснее? Вот что выводится в консоль:

==========================================================
:::::::::::  myObject  ::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
a: 10
__constructor__: [type Function]
constructor: [type Function]
__proto__: [object Object]
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::  myObject.__proto__  ::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
__proto__: undefined
toLocaleString: [type Function]
isPropertyEnumerable: [type Function]
isPrototypeOf: [type Function]
hasOwnProperty: [type Function]
toString: [type Function]
valueOf: [type Function]
addProperty: [type Function]
unwatch: [type Function]
watch: [type Function]
constructor: [type Function]
==========================================================
:::::::::::  someArray  ::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
3: 7
2: 4
1: 2
0: 1
__proto__: 
constructor: [type Function]
length: 4
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::  someArray.__proto__  ::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
sortOn: [type Function]
reverse: [type Function]
sort: [type Function]
toString: [type Function]
splice: [type Function]
join: [type Function]
slice: [type Function]
unshift: [type Function]
shift: [type Function]
concat: [type Function]
pop: [type Function]
push: [type Function]
__proto__: [object Object]
constructor: [type Function]
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::  someArray.__proto__.__proto__  ::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
__proto__: undefined
toLocaleString: [type Function]
isPropertyEnumerable: [type Function]
isPrototypeOf: [type Function]
hasOwnProperty: [type Function]
toString: [type Function]
valueOf: [type Function]
addProperty: [type Function]
unwatch: [type Function]
watch: [type Function]
constructor: [type Function]
==========================================================
:::::::::::  Примитивная строка  ::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::  Примитивная строка.__proto__  ::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
substr: [type Function]
split: [type Function]
substring: [type Function]
slice: [type Function]
lastIndexOf: [type Function]
indexOf: [type Function]
concat: [type Function]
charCodeAt: [type Function]
charAt: [type Function]
toLowerCase: [type Function]
toUpperCase: [type Function]
toString: [type Function]
valueOf: [type Function]
__proto__: [object Object]
constructor: [type Function]
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::  Примитивная строка.__proto__.__proto__  ::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
__proto__: undefined
toLocaleString: [type Function]
isPropertyEnumerable: [type Function]
isPrototypeOf: [type Function]
hasOwnProperty: [type Function]
toString: [type Function]
valueOf: [type Function]
addProperty: [type Function]
unwatch: [type Function]
watch: [type Function]
constructor: [type Function]
==========================================================
:::::::::::  _root  ::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
toLocaleString: [type Function]
isPropertyEnumerable: [type Function]
isPrototypeOf: [type Function]
hasOwnProperty: [type Function]
toString: [type Function]
valueOf: [type Function]
addProperty: [type Function]
unwatch: [type Function]
watch: [type Function]
a: Примитивная строка 
someArray: 1,2,4,7
myObject: [object Object]
__proto__: [object Object]
constructor: [type Function]
$appPath: file:///K|/Program%20Files/Macromedia/Flash%20MX/
$version: WIN 6,0,21,0
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::  _root.__proto__  ::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
createTextField: [type Function]
clear: [type Function]
endFill: [type Function]
lineStyle: [type Function]
curveTo: [type Function]
lineTo: [type Function]
moveTo: [type Function]
beginGradientFill: [type Function]
beginFill: [type Function]
createEmptyMovieClip: [type Function]
stopDrag: [type Function]
startDrag: [type Function]
removeMovieClip: [type Function]
duplicateMovieClip: [type Function]
gotoAndStop: [type Function]
gotoAndPlay: [type Function]
prevFrame: [type Function]
nextFrame: [type Function]
stop: [type Function]
play: [type Function]
setMask: [type Function]
getDepth: [type Function]
attachVideo: [type Function]
attachAudio: [type Function]
getBytesLoaded: [type Function]
getBytesTotal: [type Function]
getBounds: [type Function]
hitTest: [type Function]
globalToLocal: [type Function]
localToGlobal: [type Function]
swapDepths: [type Function]
attachMovie: [type Function]
loadMovie: [type Function]
loadVariables: [type Function]
unloadMovie: [type Function]
getURL: [type Function]
meth: [type Function]
tabIndex: undefined
enabled: true
useHandCursor: true
__proto__: [object Object]
constructor: [type Function]
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::  _root.__proto__.__proto__  ::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
__proto__: undefined
toLocaleString: [type Function]
isPropertyEnumerable: [type Function]
isPrototypeOf: [type Function]
hasOwnProperty: [type Function]
toString: [type Function]
valueOf: [type Function]
addProperty: [type Function]
unwatch: [type Function]
watch: [type Function]
constructor: [type Function]
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
    

Предоставив вам самостоятельно разглядывать разнообразные недокументированные функции,

названия которых у нас сейчас распечатались (наряду со всем остальным), мы дадим все же нес

колько комментариев. Во-первых, разумеется, необязательно было снимать защиту со всех полей

и методов. Если вы будете использовать подобную функцию для распечатки содержимого собственных

классов, мы рекомендуем вам закомментировать соответствующую строчку.
Во-вторых, вы можете заметить, что наш фокус с обнулением __proto__ не сработал на клипе _ro

ot - в него все равно попали методы из класса Object. (Почему только из Object? Дело в том, что

непосредственный прототип объекта _root - это MovieClip, но снять защиту с его методов мы к

моменту вызова dumpObj для _root еще не успели. А с Object защита уже была снята.) В остальных

случаях такого не произошло. Отсюда вывод: все дело в том, что _root - слишком важный системный

объект, чтобы позволять его поведению зависеть от того, что лежит у него в __proto__. (А, может, дело

в том, как именно было удобнее всего реализовать корневой клип.) Во всяком случае, о том, что за объект

является прототипом клипа _root, система Flash MX догадывается без обращения к полю _root.__proto__ и

поэтому изменение его значения ничего не дает. О том, что это п оведение не является общим для других

клипов, можно узнать, вызвав функцию dumpObj для произвольного клипа. Для этого можно в конец

вышеприведенного кода вставить такие строчки:

_root.createEmptyMovieClip("newClip", 1);
dumpObj(newClip, "mc");
    

Только что раскрытые функции Object и MovieClip в секции, где выводятся собственные поля и методы

клипа newClip, не обнаруживаются.

Доступ к базовому классу: ключевое слово super

Вы уже знаете два способа обратиться из производного класса к базовому. Первый - с помощью apply или

call (при этом используется явное имя класса и ссылка prototype). Можно также добраться до нужных функций

прямо из объекта через this.__proto__.__proto__ (а потом опять использовать apply). Этот способ хуже, поскольку

при вызове функции, содержащей такой код, из классов, производных от нашего, значение ссылки this.__proto__.__proto__ будет другим.
Но есть в системе Флэш МХ и механизм, который специально предназначен для решения

рассматриваемой задачи. Этот механизм программистам, применяющим в своей работе язык Java,

прекрасно известен. Основан он на использовании ключевого слова super. С помощью этого ключевого

слова можно вызвать конструктор базового класса - пишем super(arg1, arg2, ...). Список аргументов ничем

не отличается от списка аргументов любой другой функции. При этом конструктор вызывается не как

обычная функция (в которой this означает объект, в котором лежит ссылка на нее), а именно как конструктор

(this указывает на вновь созданный объект). Если же нужно обратиться к какому-либо полю или методу

базового класса, используется конструкция вида super.a или super .func() (что, как правило, применяется

при вызове базового варианта переопределяемой виртуальной функции).
Обратите внимание, что super - это не просто ссылка, также как и this, поскольку имеет специальное

значение в полиморфных классах. Ведь super, использованный в методе некоторого класса, всегда будет

означать ссылку на его непосредственного предка, даже когда вызов произошел из далекого производного

класса (в методах которого super имеет совсем другое значение). Вы можете представлять себе это так:

компилятор Флэш, видя в коде ключевое слово super, вставляет на его место нечто вроде ссылки

непосредственно на базовый класс (его прототип). В случае вызова конструктора эта псевдоссылка будет указывать на сам конструктор базового класса.
Последняя особенность "ссылки" super вытекает из предыдущих: перед ней не надо писать this.

Когда нужно применять явное указание базового класса

Мы теперь знаем, что обратиться к методу базового класса можно, используя ключевое слово super.

Но в длинных иерархиях иногда полезно вызвать метод суперкласса более высокого уровня (так что

наш класс является его весьма дальним наследником). Как это сделать? Цепочки типа super.super не

только неудобны - они еще и не работают, поскольку super не является полем. Вот пример, доказывающий это.

superclass = function(){}
superclass.prototype = new Object();
superclass.prototype.say = function(text){trace(text + 
                           "superclass")} 
subclass1 = function(){super.say();}
subclass1.prototype = new superclass();
subclass1.prototype.say = function(text){trace(text + 
                           "subclass1")} 
subclass2 = function(){super.say();}
subclass2.prototype = new subclass1();
subclass2.prototype.say = function(text){trace(text + 
                           "subclass2")} 
subclass3 = function(){super.say();}
subclass3.prototype = new subclass2();
subclass3.prototype.say = function(text){trace(text + 
                           "subclass3")}
subclass3.prototype.say2 = function(text){super.say(text);}
subclass3.prototype.say3 = function(text){super.super.say(text);}
subclass3.prototype.say4 = function(text){super.super.super.say(text);}
a = new subclass3();
trace("---------------");
a.say("type = ");
a.say2("type = ");
a.say3("type = ");
a.say4("type = ");
        

Эта программа выводит следующее:

superclass
subclass1
subclass2
---------------
type = subclass3
type = subclass2
        

Мы видим, что все цепочки типа super.super не сработали. Что же делать? В С++ в таких случаях можно

было явно указать имя базового класса. Во ФлэшМХ такого механизма нет, но мы знаем, что средствами

Флэш разрешима более общая задача - можно вызывать функцию, методом класса вовсе не являющуюся

так, как если бы она этим методом была. Как вы, наверное, помните, для этого используются методы

класса Function по имени call и apply. В случае, когда мы переопределяем функцию базового класса,

бывает удобнее пользоваться методом apply, где аргументы не перечисляются друг за другом, а

передаются целиком в объекте arguments.В примере, приведенном в начале параграфа, описание к

ласса subclass3 можно сделать таким образом:

subclass3 = function(){super.say();}
subclass3.prototype = new subclass2();
subclass3.prototype.say = function(text){trace(text + 
   "subclass3")}
subclass3.prototype.say2 = function(text){subclass2.
   prototype.say.apply(this, arguments);}
subclass3.prototype.say3 = function(text){subclass1.
   prototype.say.apply(this, arguments);}
subclass3.prototype.say4 = function(text){superclass.
prototype.say.apply(this, arguments);}
        

На сей раз вывод программы будет таким:

superclass
subclass1
subclass2
---------------
type = subclass3
type = subclass2
type = subclass1
type = superclass
        

То есть теперь все работает как надо.
Чтобы завершить эту тему, скажем еще о том, что вызов конструкторов далеких предков (если

они почему-то не были вызваны своими непосредственными потомками) вам тоже придется

производить, пользуясь вышеописанным способом.

"Лишние" вызовы конструкторов

При анализе двух предыдущих примеров вы могли заметить, что на печать выводится нескольком начале.

Можно ли обойтись без вызова конструкторов при наследовании? Оказывается, есть обходные пути.

Мы обсудим их в параграфе, посвященном альтернативному наследованию и "альянсу мятежников".

Проверка типа

В некоторых случаях бывает необходимо проверить, является ли данный объект наследником

некоторого класса. Хотя в грамотно спроектированной иерархии классов подобная надобность

возникает нечасто. Но иногда вы пользуетесь чужими классами, менять которые нет ни желания,

ни необходимости. Тогда на помощь приходит оператор instanceof. Пример: if (arg instanceof MovieClip) arg.x = 10;
Оператор instanceof - это один из способов отличить примитивные типы от объектных (второй

способ - попробовать завести у объекта данного типа поля). Рассмотрим такой код:

a = new Number(5);
b = 5;
a.x = 3;
b.x = 4;
trace(a instanceof Number);
trace(b instanceof Number);
trace(a.x);
trace(b.x);
            

То есть мы производим одни и те же действия над объектами а и b, причем первый из них -

объектного типа, а второй - примитивного. А вот и результат:

true
false
3
undefined
            

Мы видим, что объект b к типу Number не принадлежит и попытка завести у него поля хотя и не

приводит к ошибке, но и результата никакого не дает.
Наконец, чтобы отличить переменную, в которой записано примитивное значение 5 от Number(5),

мы можем проверить, на что указывает поле __proto__ нашего объекта. В случае Number оно указывает

на Number.prototype, в противном случае его значением будет undefined. Эти рассуждения наводят на

мысль о том, что функцию, подобную instanceof, можно сделать самостоятельно. И действительно, в

онлайн-документации Флэш МХ приведен следующий пример (мы меняем только название функции,

чтобы оно не совпадало с ключевым словом):

function emulated_instanceof (theObject, theClass){
   while ((theObject = theObject.__proto__) != null) {
      if (theObject == theClass.prototype) {
         return true;
      }
   }
   return false;
}
            

Давайте разберем этот пример подробнее, поскольку на нем вполне можно проверить, прави

следует проверить не сам текущий объект, а уже его прототип, и так далее, пока цепочка не приведет

нас к Object'у. Таким образом, рассматриваемая функция действительно полностью идентична оператору instanceof.

Изменение базовых классов

Прототипная модель наследования (вкупе с интерпретируемым языком) имеет одно неожиданное

свойство. Мы можем вносить какие-то изменения в базовые классы уже после того, как объекты

производных классов созданы и используются. После того, как эти изменения будут внесены,

поведение всех объектов производных классов сразу же поменяется. Такую интересную возможность

грех не использовать. И сейчас мы покажем, как это делается. (Хотя самые яркие примеры вы увидите

позже в лекции об эмуляции множественного наследования).

Пример изменения работы иерархии

Вот совсем простой пример изменения работы иерархии классов. Мы создаем два класса (один -

наследник другого). И заводим в базовом классе функцию, которая выводит в консоль все поля

объекта. Затем, испробовав, как эта функция работает, заменяем ее другой. И снова смотрим, каков

результат ее вызова из объекта базового и производного классов. Вот код, который реализует эти простые идеи.

_global.Base = function(a, b){
   this.a = a;
   this.b = b;
}
_global.Base.prototype.printAll = function(){
   for (var name in this){
      trace("this." + name + " = " + this[name]);
   }
}
_global.Derived = function(a, b, c, d){
   super(a, b);
   this.c = c;
   this.d = d;
}
_global.Derived.prototype = new Base();
b = new Base(10, 20);
d = new Derived(15, 25, 35, 45);
trace("================ b ===============");
b.printAll();
trace("==================================");
trace("================ d ===============");
d.printAll();
trace("==================================");
_global.Base.prototype.printAll = function(){
   trace("Function is obsolete!");
}
trace("================ b ===============");
b.printAll();
trace("==================================");
trace("================ d ===============");
d.printAll();
trace("==================================");
        

На выходе получаем:

================ b ===============
this.printAll = [type Function]
this.b = 20
this.a = 10
==================================
================ d ===============
this.printAll = [type Function]
this.d = 45
this.c = 35
this.b = 25
this.a = 15
==================================
================ b ===============
Function is obsolete!
==================================
================ d ===============
Function is obsolete!
==================================
Функция  действительно заменилась - как в базовом классе,  так и в производном. 
В следующем подпараграфе мы  приведем уже более близкие к практике примеры использования этой методики.

Добавление функций в Object
Чаще всего динамическое изменение иерархии применяется для заведения функций, в чем-то аналогичных глобальным. Точнее, для создания методов, применимых к любому объекту. Для этого приходится менять самый базовый класс иерархии, то есть Object.
В качестве простого примера мы сейчас внедрим в Object слегка модифицированную функцию printAll(), которая фигурировала у нас в предыдущем подпараграфе. Мы лишь добавим ей аргумент - строку с именем объекта, поля которого она выводит. Вот каким в результате будет код нашего примера:
Object.prototype.printAll = function(obj){
if (obj == undefined) obj = "[Object]";
for (var name in this){
trace(obj + "." + name + " = " + this[name]);
}
}

a = [1, 3, 5, 7];
a.printAll("a");

На выходе получим следующее:
a.printAll = [type Function]
a.3 = 7
a.2 = 5
a.1 = 3
a.0 = 1

Таким образом, создав совершенно произвольный массив, мы обнаружили в нем функцию, добавленную в класс Object. Согласитесь, что инструмент, который мы только что опробовали, весьма мощный, и открывающиеся перспективы поражают нетренированное воображение. Еще раз отметим, что добавить методы в базовый класс мы можем в любой момент - и воспользоваться плодами этого в производных классах можно будет немедленно.
А теперь пару слов о "правилах хорошего тона". Мы видим, что при перечислении полей произвольного объекта обнаруживается свежедобавленная функция printAll. Ничего хорошего в этом нет; особенно такие вещи будут раздражать программистов, использующих ваш код, у которых вдруг ваши функции начнут появляться в каждом отладочном дампе полей произвольного объекта. Поэтому следует воспользоваться известной нам недокументированной функцией ASSetPropFlags и спрятать новый метод так же, как спрятаны системные. Правильный код будет вот таким:
Object.prototype.printAll = function(obj){
if (obj == undefined) obj = "[Object]";
for (var name in this){
trace(obj + "." + name + " = " + this[name]);
}
}
// Прячем новую функцию от for...in
ASSetPropFlags(Object.prototype, "printAll", 1);

a = [1, 3, 5, 7];
a.printAll("a");

и на выходе мы, разумеется, получим
a.3 = 7
a.2 = 5
a.1 = 3
a.0 = 1

То есть теперь выводятся только необходимые нам данные, без всяких следов самой функции printAll.
Клонирование объектов-функций
Еще один базовый класс, в который часто добавляют новые методы - это Function. Мы с вами уже сталкивались с этим приемом, когда обсуждали способы создания статических и приватных свойств. Теперь мы рассмотрим еще один пример. Предположим, создавая свои функции, вы заводите у каждой функции-объекта поле name (с тем, чтобы использовать его в отладочных целях). Это удобно ровно до тех пор, пока вы не решите записать ссылку на функцию-объект в поле с другим именем. (Зачем такое может понадобиться? Например, вы хотите, чтобы два ваших класса из разных иерархий имели общую функцию, а возиться с множественным наследованием ради одной функции у вас нет охоты. Названия же этих функций могут быть заданы заранее в каждой из иерархий, если эти функции переопределяют некоторые виртуальные функции из базовых классов). Проблема, которая возникает в таком случае, вполне очевидна: поскольку эти (одинаковые) методы называются по-разному, то и поле name у каждой из этих функций-объектов должно быть свое. А ведь мы собрались сделать две ссылки, ссылающиеся на один и тот же объект - понятно, что в таком случае сделать поля name разными будет невозможно. Выходит, объекты нам придется сделать разными. (Но, в конечном счете, должна вызываться одна и та же функция). Конечно, создавать новый объект-функцию при помощи конструкции = function всякий раз вручную вовсе несложно. Тем не менее, вам может показаться более удобным заранее создать в Function.prototype метод clone, который заодно и поле name установит в нужное значение. (Аналогичным образом приходится действовать во многих случаях, когда мы храним какую-либо информацию в полях объектов-функций). Итак, вот код, который реализуем намеченный нами алгоритм.
Function.prototype.clone = function(newName, owner){
// Сохраняем сылку на клонируемую функцию
// для использования внутри функции-клона.
var thisFunc = this;
var newFunc = function(){
// Добавляем аргументы, чтобы поддержать отладочные возможности.
return thisFunc.apply(owner,
arguments.concat(["cloned_func", arguments]));
}
for (var fieldName in this){
newFunc[fieldName] = this[fieldName];
}
newFunc.name = newName;
return newFunc;
}
// Прячем новую функцию от for...in
ASSetPropFlags(Function.prototype, "clone", 1);
// Отладочная функция для определения имени и аргументов
// функции, находящейся в стеке вызовов на 2 уровня выше.
// (Для того, чтобы узнать, что творится на 1 уровень выше
// писать специальную функцию не нужно).
_global.whereWeAre = function(caller_args){
var funcName, argsNum = caller_args.length;
var firstCallerArgs = caller_args;
trace("--------------------------");
// Ищем объект arguments той функции, которая вызвала
// отлаживаемую (внутрь отлаживаемой мы поместим вызов
// функции whereWeAre). Если использовались отладочные
// аргументы, то найти его просто.
if (firstCallerArgs[firstCallerArgs.length - 2] == "caller_args"){
firstCallerArgs = firstCallerArgs[firstCallerArgs.length
- 1];

// Учтем возможность клонирования функций. Мы должны пройти
// по цепочке клонов до самой последней функции-клона,
// имя которой нам и необходимо узнать.
while(firstCallerArgs[firstCallerArgs.length - 2] == "cloned_func"){
firstCallerArgs = firstCallerArgs[firstCallerArgs.length
- 1];
}

// Найдя нужную функцию, запоминаем ее имя,
// а также количество аргументов.
funcName = firstCallerArgs.callee.name + " ";
argsNum = firstCallerArgs.length;
}
else{
// Однако, возможно, что отлаживаемая функция была вызвана
// без отладочных аргументов. В таком случае доступно только
// имя вызывающей функции, но не ее аргументы.
funcName = firstCallerArgs.caller.name;
if (funcName != undefined){
trace("Мы находимся в функции " + funcName);
}
else{
trace("Имя вызывающей функции недоступно.");
}
trace("Агрументы вызывающей функции недоступны.");            
trace("==============================================\n");
return;
}
var hasArgs = "с аргументами:";
// Отладочные аргументы не считаются
if (argsNum <= 0) hasArgs = "без аргументов."
trace("Мы находимся в функции " + funcName + hasArgs);
for(var i=0; i<argsNum; i++){
trace("arguments[" + i + "]=
" + firstCallerArgs[i]);      
}
trace("==============================================\n");
}
// Теперь тестируем, что у нас получилось.
// Для пробы вызываем whereWeAre просто так.
whereWeAre();
// Эта функция будет играть роль отлаживаемой.
// В нее мы помещаем вызов whereWeAre.
function f(){
trace("Вызвана внутренняя функция f()");
whereWeAre(arguments);
}
// Эта функция будет играть роль внешней.
// Ее имя и аргументы мы будем стараться  вывести с помощью
// whereWeAre. И ее же мы будем клонировать.
function g(a, b){
trace("Вызвана внешняя функция");
f(a+1, b+1, "caller_args", arguments);
}
g.name = "g()";
// Клонируем.
g1 = g.clone("g1()", this);
// Вызываем обычную и клонированную функции.
g(1, 2);
g1(3, 4);
// А в этой функции мы проверим, как whereWeAre справится
// с отсутствием отладочных аргументов.
strangeFunc = function(){
trace("Вызвана еще одна функция, в которой не учтены");
trace("наши новые отладочные возможности.");
f();
}
strangeFunc.name = "strangeFunc()";
strangeFunc("Some argument");

Вот что мы получаем, запустив этот код на выполнение:
--------------------------
Имя вызывающей функции недоступно.
Агрументы вызывающей функции недоступны.
====================================================

Вызвана внешняя функция
Вызвана внутренняя функция f()
--------------------------
Мы находимся в функции g() с аргументами:
arguments[0]= 1
arguments[1]= 2
====================================================

Вызвана внешняя функция
Вызвана внутренняя функция f()
--------------------------
Мы находимся в функции g1() с аргументами:
arguments[0]= 3
arguments[1]= 4
====================================================

Вызвана еще одна функция, в которой не учтены
наши новые отладочные возможности.
Вызвана внутренняя функция f()
--------------------------
Мы находимся в функции strangeFunc()
Агрументы вызывающей функции недоступны.
====================================================

Видим, что клонирование замечательно работает. Осталось лишь еще раз напомнить, что функции, добавляемые в системные иерархии классов, обязательно надо прятать от for...in (иначе, как мы уже говорили, они будут появляться при переборе полей любого объекта этого или производного класса - а зачем вам такое нужно?)

Альтернативное наследование ("альянс мятежников")

Мы познакомились с тем интересным способом (называемым "прототипным наследованием"), с помощью которого делается наследование во Флэш МХ. Однако вы уже, видимо, заметили в этом способе некоторое неудобство. Состоит оно в том, что при создании объекта базового класса, записываемого в прототип класса производного, неминуемо вызывается конструктор. Сейчас мы приведем слегка модифицированный (и укороченный) пример наследования, уже встречавшийся в этой лекции, и затем попытаемся его исправить.

superclass = function(){
   trace("constructor of superclass");
}
superclass.prototype.say = function(text){trace(text + 
   "superclass")} 
subclass1 = function(){
   super();
   super.say();
}
// Вот обычное наследование 
subclass1.prototype = new superclass();
subclass1.prototype.say = function(text){trace(text + 
   "subclass1")} 
a = new subclass1();
trace("---------------");
a.say("type = ");
            

Запустим это код и получим:

constructor of superclass
constructor of superclass
superclass
---------------
type = subclass1
            

Мы еще раз убедились, что обычное наследование неудобно из-за двойного вызова конструкторов. Но есть ряд вещей, которые нам помогут избежать этого! Во-первых, учтем, что при создании функции сразу же создается дополнительный пустой объект, на который указывает ссылка prototype. То есть при создании самого базового класса вовсе необязательно писать superclass.prototype = new Object() (и этим фактом мы регулярно пользовались). Во-вторых, нам надо добиться, чтобы объект, на который указывает prototype, воспринимался как объект определенного класса (базового); этот объект мы будем затем модифицировать. Мы знаем, что для этого нужна запись наподобие subclass1.prototype.__proto__ = superclass.prototype. Наконец, мы говорили, что выполнение оператора new не только создает новый объект и устанавливает ему свойство __proto__, но и инициализирует новому объекту еще два свойства по имени constructor и __constructor__. Свойство constructor нам сейчас не понадобится, а вот без __constructor__ не работает вызов базового конструктора через super(). Соответственно, именно на конструктор базового класса и должен указывать __constructor__. Итак, модифицируем вышеприведенный код следующим образом:

superclass = function(){
   trace("constructor of superclass");
}
superclass.prototype.say = function(text){trace(text + 
   "superclass")} 
subclass1 = function(){
   super();
   super.say();
}
// Следующие две строки - это и есть альтернативное наследование
subclass1.prototype.__proto__ = superclass.prototype;
subclass1.prototype.__constructor__ = superclass;
subclass1.prototype.say = function(text){trace(text + 
   "subclass1")} 
a = new subclass1();
trace("---------------");
a.say("type = ");
            

Запускаем этот код и получаем:

constructor of superclass
superclass
---------------
type = subclass1
            

Этот способ наследования действительно помог разрешить нам проблему двойного вызова конструкторов. Если он вам понравился, вы, возможно, захотите записывать наследование при помощи одного оператора вместо двух. В таком случае можете сделать специальную функцию (мы по традиции, принятой в среди сторонников такого наследования, называем ее cExtends, что является сокращением от custom extends), которая будет делать то, что мы написали выше. Эту функцию можно положить прямо в прототип класса Function, чтобы его можно было вызвать из любого объекта-функции. Вот как это делается:

Function.prototype.cExtends = function(base){
   this.prototype.__proto__ = base.prototype;
   this.prototype.__constructor__ = base; 
}
superclass = function(){
   trace("constructor of superclass");
}
superclass.prototype.say = function(text){trace(text + 
   "superclass")} 
subclass1 = function(){
   super();
   super.say();
}
// Сокращенная форма альтернативного наследования
subclass1.cExtends(superclass);
subclass1.prototype.say = function(text){trace(text + 
   "subclass1")} 
a = new subclass1();
trace("---------------");
a.say("type = ");
            

Запустив этот код, мы получим то же самое, что и в прошлый раз - функция cExtends замечательным образом сработала. Но нет предела совершенству - можно еще улучшить нашу замечательную функцию cExtends. Во-первых, неплохо бы сделать, чтобы поле __constructor__ было скрыто - так же, как и в том случае, когда его создает оператор new. Для этого надо в cExtends добавить строчку

ASSetPropFlags(this.prototype, "__constructor__", 1);
            

Для __proto__ ничего такого делать не надо - это свойство уже присутствует (и является скрытым) в prototype с самого начала. Во-вторых, вы, наверное, помните, что в прошлой лекции мы обсуждали, как сделать статические (принадлежащие целому классу) свойства. При этом свойства у нас хранились в полях функции-конструктора класса. Можно ли сделать эти статические свойства наследуемыми? Оказывается, да, и для этого функцию cExtends нужно модифицировать следующим образом:

Function.prototype.cExtends = function(base){
   this.prototype.__proto__ = base.prototype;
   this.prototype.__constructor__ = base; 
   ASSetPropFlags(this.prototype, "__constructor__", 1);
   this.__proto__ = base;
}
            

В самом деле, после этой модификации функция-объект, для которой был вызван метод cExtends, становится как бы наследником функции-объекта, являющейся конструктором базового класса (и в полях которой лежат статические свойства). То есть для того, чтобы можно было наследовать статические свойства, мы связали отношением наследования не только прототипы, но и объекты-конструкторы!
Приведенный только что вариант функции cExtends придуман опять-таки T. Гролео. Он же приводит в своей статьедовольно романтическую историю изобретения альтернативного наследования. Мы дополнили эту историю своими изысканиями, и сейчас сообщим ее вам. Группа исследователей Флэш под предводительством Дэйва Янга (чью статью об альтернативном наследовании вы также можете почитать вот по этой ссылке:), собравшаяся вокруг сайта FlashСoders, поставила себе целью разработать механизм наследования во Флэше, лишенный недостатков стандартного варианта. Эта группа взяла себе название Rebel Alliance ("альянс мятежников") - по мотивам Rebel Alliance из "Звездных войн". Было придумано несколько различных вариантов наследования. Однако все они имели определенные недостатки. Наконец, молодой джедай Питер Эдвардс обнаружил, что единственная серьезная дырка наследования при помощи __proto__ - невозможность вызвать базовый конструктор через super() - затыкается с помощью недокументированной ссылки __constructor__. После этого подвига даже выдвигаласть идея предложить Эдвардсу пост магистра Йоды (невзирая на то, что Эдвардс наверняка не подходит, ибо настоящий Йода должен быть исключительно мал ростом и очень уродлив). Так или иначе, альянс одержал убедительную победу.
Однако, похоже, что империя нанесла ответный удар. Сайт FlashСodersзакрыт (а ведь на него указывали едва ли не все ссылки Интернета, касающиеся недокументированных возможностей Флэш). Закрыты и остальные сайты подобного плана (например сайт, с которого можно было скачать онлайн-документацию по недокументированным функциям - в формате, пригодном для установки в среду Флэш МХ). Последним прибежищем истинных джедаев остается интернет-архив, на котором, в частности сохранились документы конференции flashcoders-wiki. Архив заглавной страницы находится здесь: /.
Еще остался список рассылки Flashcoders, подписаться на который можно здесь:, а вот здесьнаходится его архив переписки.

Так что не будем унывать - во-первых, мы-то с вами знаем все, что нужно, дабы устраивать всякие необычные фокусы во Флэше (без них настоящему программисту и жизнь не мила)! Во-вторых, документированных возможностей гораздо больше, чем недокументированных, так что потери не так уж велики. В-третьих, вы, наверное, в курсе, чем закончились "Звездные войны". Победа будет за нами!

 

 
На главную | Содержание | < Назад....Вперёд >
С вопросами и предложениями можно обращаться по nicivas@bk.ru. 2013 г.Яндекс.Метрика