Опубликован: 23.12.2005 | Уровень: специалист | Доступ: платный | ВУЗ: Московский физико-технический институт
Лекция 7:

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

алексеи федорович
алексеи федорович
Беларусь, рогачёв
Тамара Ионова
Тамара Ионова
Россия, Нижний Новгород, НГПУ, 2009