| Беларусь, рогачёв |
Наследование во Flash MX
Добавление функций в 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 (иначе, как мы уже говорили, они будут появляться при переборе полей любого объекта этого или производного класса - а зачем вам такое нужно?)