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