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

Классы

Аннотация: Объектная модель ActionScript 1.0: прототипное наследование. Понятие прототипа, способы эмуляции прототипа. Конструктор и его двоякая роль в прототипном наследовании. Изучение деталей функционирования прототипного наследования при помощи его эмуляции. Доступ к скрытым полям и методам при помощи недокументированной функции ASSetPropFlags. Механизмы взаимодействия объекта и прототипа. Скрытые поля __proto__, constructor и __constructor__. Механизм copy on write при обращении к полям объекта. Способы эмуляции статических и приватных полей и методов.

Классы в ActionScript (в отличие от ActionScript 2) реализованы довольно нестандартным способом. Причина этого в том, что объектная модель первых трех версий ECMAScript ведет свою родословную не от С++, а от языка Self, в котором она, в свою очередь, является развитием конструкций из некоторых вариантов Smalltalk. Это так называемая прототипная модель, которая, как мы увидим дальше, хорошо подходит для интерпретируемых языков и использует те их преимущества, о которых мы уже вкратце говорили. Хорошая расширяемость такой объектной модели позволит нам эмулировать ряд аспектов множественного наследования, которое изначально в ActionScript не было предусмотрено.

Понятие прототипа

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

Прототип - обычный объект

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

lift = {};
lift.currentFloor = 1;      // Текущий этаж
lift.floorToGo = 1;       // Этаж назначения
lift.minFloor = 1;        // Минимальный этаж
lift.maxFloor = 9;        // Максимальный этаж
lift.doorsAreOpen = true;  // Двери открыты
lift.goto = function(where){
	trace("-----------"); 
	// Округляем и ограничиваем этаж назначения
	this.floorToGo = Math.min(Math.max(Math.round(where), 
		this.minFloor), this.maxFloor);
	if (this.floorToGo != this.currentFloor) this.go();//Поехали!
	else trace("Ничего не делаем." );
	trace("-----------");
}
lift.setDoorsOpen = function(open){
	if (this.doorsAreOpen != open){
		trace(open ? "Открываем двери." : "Закрываем двери.")
	}
	this.doorsAreOpen = open;
}
lift.go = function(){
	this.setDoorsOpen(false);
	var distance = this.floorToGo - this.currentFloor;
	var signOfDistance = (distance >= 0 ? 1 : -1);
	for(  // Это работает независимо от того, едем вверх или вниз
		var i = this.currentFloor; 
		signOfDistance*i <= signOfDistance*this.floorToGo; 
		i += signOfDistance
	){
		trace("Этаж " + i);
		this.currentFloor = i;
	}
	this.setDoorsOpen(true);
}
 // Тестируем
lift.goto(5);
lift.goto(3);
lift.goto(10);
lift.goto(9);

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

-----------
Закрываем двери.
Этаж 1
Этаж 2
Этаж 3
Этаж 4
Этаж 5
Открываем двери.
-----------
-----------
Закрываем двери.
Этаж 5
Этаж 4
Этаж 3
Открываем двери.
-----------
-----------
Закрываем двери.
Этаж 3
Этаж 4
Этаж 5
Этаж 6
Этаж 7
Этаж 8
Этаж 9
Открываем двери.
-----------
-----------
Ничего не делаем.
-----------

Лифт работает! Теперь посмотрим, сможем ли мы построить класс на основе этого объекта.

Упрощенное представление - копируем прототип

Наличие класса означает, что мы можем создавать множество независимых друг от друга объектов этого класса. Очевидно, что наивная запись lift2 = lift к добру не приведет - мы получим лишь еще одну ссылку на тот же самый объект. (Можно сказать, что эта запись делает два разных входа в здание с лифтом; однако, войдя через другой вход в дом мы все равно обнаружим лифт на том же самом этаже. Мы же хотим иметь два разных лифта в разных домах.) В самом деле, написав вместо старых операторов тестирования следующие строчки:

lift2 = lift;
	// Проверяем
trace("lift:");
lift.goto(3);
trace("lift2:");
lift2.goto(5);

получим:

lift:
-----------
Закрываем двери.
Этаж 1
Этаж 2
Этаж 3
Открываем двери.
-----------
lift2:
-----------
Закрываем двери.
Этаж 3
Этаж 4
Этаж 5
Открываем двери.
-----------

Действительно, мы имеем дело только с одним лифтом - на пятый этаж lift2 поехал не с первого этажа (как было бы, если бы мы в самом деле создали новый лифт) а с третьего.

Таким образом, нам нужно как-то скопировать объект. Вот первый (несовершенный) вариант:

// Функция для копирования
_global.newObject = function(objectPrototype){
	var tempObject = {};  // Создаем новый объект
	for(fieldName in objectPrototype)  // Копируем (?) поля
		tempObject[fieldName] = objectPrototype[fieldName];
	return tempObject;
}
	// Копируем лифт
lift2 = newObject(lift);
	// Проверяем
trace("lift:");
lift.goto(3);
trace("lift2:");
lift2.goto(5);

Добавив этот код к коду из предыдущего подпараграфа (и убрав старые "пробные запуски" лифта), получаем на выходе:

lift:
-----------
Закрываем двери.
Этаж 1
Этаж 2
Этаж 3
Открываем двери.
-----------
lift2:
-----------
Закрываем двери.
Этаж 1
Этаж 2
Этаж 3
Этаж 4
Этаж 5
Открываем двери.
-----------

Действительно, лифты ездят независимо друг от друга.

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