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

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

Работа с прототипами различных уровней

Итак, мы видим, что имея объект, можно обратиться как к его собственным полям, так и к полям прототипа, полям прототипа прототипа и т.д. Причем, такое обращение может осуществляться как автоматически (при поиске поля или функции), так и вручную при помощи цепочки ссылок вида __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);

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