Нарядная инициализация AS1 классов.

AS2 классы привнесли новые понятия в программирование на ActionScript. Приватные и публичные методы, статические члены класса и т.п. Однако, тот факт, что AS2 класс может быть откомпилен в 6ю версию плеера говорит от том, что в самой сути кода ничего не изменилось, что всё это реализуемо в AS1. Как? Читаем дальше >>

Для начала хочется прибить читателя кодом:

#initclip
this.set$$$Class = function() {
delete this.set$$$Class;
var $$$Class = _global.$$$=function () {
this.init();
};
Object.registerClass('$$$_mc', $$$Class);
ASSetPropFlags(_global, "$$$", 7, 1);
var tmp = $$$Class.prototype=new MovieClip();
tmp.init = function() {
};
};
this.set$$$Class();
#endinitclip

- не правда ли, странный код? Как думаете, сколько времени у меня ушло на его написание? Доли секунды. Во флэше, когда курсор на редакторе кода, я последовательно нажал три клавиши: Esc+s+c и получил этот код.
Не стоит пока пытаться повторить этот подвиг. Чтобы фокус удался, нам нужно задать эскейп-последовательность.

Задаем эскейп-последовательность
Для начала нужно провести небольшие манипуляции с файлом ActionsPanel.xml.
Он лежит в папочке C:\Documents and Settings\ИмяЮзера\Local Settings\Application Data\Macromedia\Flash MX 2004\en\Configuration\ActionsPanel\
- разумеется ИмяЮзера это ваше имя пользователя на этом компе.
Итак, открываем (не ленимся!) ActionsPanel.xml и практически вначале, после строки

<folder name="Global Functions" id="Actions"........................

втыкаем следующий XML узел:

<folder name="Personal" id="Personal" tiptext="Presonal" helpid="">
<action name="setClass" tiptext="setClass" helpid="" text="#initclip\nthis.set$$$Class = function() {\n delete this.set$$$Class;\n var $$$Class = _global.$$$ = function () {\n this.init();\n };\n Object.registerClass('$$$_mc', $$$Class);\n ASSetPropFlags(_global, '$$$', 7, 1);\n var tmp = $$$Class.prototype=new MovieClip();\n tmp.init = function() {\n };\n};\nthis.set$$$Class();\n #endinitclip" quickey="sc"/>
</folder>

Сохраняем XML, перезапускаем редактор Flash. Всё. Ескейп-последовательность добавлена. Теперь, при последовательном наботе Esc+s+c в окне редактора вставится заготовка класса.
Дальше-проще. Например мы хотим создать знаменитый по всем детским учебникам класс Ball.
Жмем Ctrl+h (поиск и замена). Водим $$$ вверху и Ball внизу. Получаем результат:

#initclip
this.setBallClass = function() {
delete this.setBallClass;
var BallClass = _global.Ball = function () {
this.init();
};
Object.registerClass('Ball_mc', BallClass);
ASSetPropFlags(_global, 'Ball', 7, 1);
var tmp = BallClass.prototype=new MovieClip();
tmp.init = function() {
};
};
this.setBallClass();
#endinitclip

Теперь, собственно, можно приступить к изучению того, что мы натворили.

Приватные объекты
Первое и главное: Класс задается внутри метода-инициализатора класса, в данном случае это setBallClass. Этот метод удаляет ссылку на себя во время вызова, чтобы небыло лишнего мусора. Однако, сам метод не удаляется, он остается жить в памяти, вечная ему память за это!
Зачем такие замороки? Это позволит нам иметь приватные, т.е недоступные снаружи переменные, объекты и методы. Для дальнейших экспериментов модифицируем класс, снесем #initclip и #endinitclip, сделаем класс наследником Object, соответственно удалим registerClass.
Затем добавим локальную переменную my_array и присвоим ей ссылку на массив.
Сделаем внутри метода init вызов trace с использованием ссылки на массив.
И в конце создадим экземпляр класса. Получим результат:

this.setBallClass = function() {
delete this.setBallClass;
var BallClass = _global.Ball=function () {
this.init();
};
ASSetPropFlags(_global, 'Ball', 7, 1);
var tmp = BallClass.prototype={};
var my_array = ["Hello, ", "world!"];
tmp.init = function() {
trace(my_array[0]+my_array[1]);
};
};
this.setBallClass();
// TEST
foo = new Ball()// Hello, world!

Самое время заглянуть в листинг переменных. Ничего кроме созданного экземпляра класса мы не увидим. _global.Ball закрыт с помощью ASSetPropFlags, а массив my_array хоть и существует в памяти, но добраться до него невозможно, иначе, как из методов класса Ball.
Вот вам и приватность. Это ооочень удобно. Правильная организация приватных/публичных объектов, когда у класса не торчат лишние уши, это здорово!
Эти уши никакой другой класс или объект случайно никогда не чикнет.
И к приватным объектам очень удобно обращаться: не нужно соображать по какому пути они находятся, они локальны и доступны в любом месте кода класса.
Ссылки типа this.constructor легко заменяются на BallClass, который объявлен как локальная переменная. Сылки this.__proto__ заменяются на tmp.

Статические методы класса.
Сносим весь код, создаем мувик, обзываем его ball_mc, задаем такой же Linkage, рисуем в нем квадратик (всем назло) и слоем выше жмем волшебную комбинацию клавиш: Esc+s+c. Результат модифицируем ручками так, чтобы получилось следующее:

#initclip
this.setBallClass = function() {
delete this.setBallClass;
var BallClass = _global.Ball=function () {
this.init();
};
Object.registerClass('ball_mc', BallClass);
ASSetPropFlags(_global, 'Ball', 7, 1);
var tmp = BallClass.prototype=new MovieClip();
tmp.max_width = 100;
tmp.max_height = 100;
var step = 2;
BallClass.create = function(thisObj, name, depth, initObj) {
var mc = thisObj.createEmptyMovieClip(name, depth);
mc.beginFill(0, 100),
mc.lineTo(10, 0), mc.lineTo(10, 10), mc.lineTo(0, 10),
mc.endFill();
for (var i in initObj) {
mc = initObj;
}
mc.__proto__ = tmp;
BallClass.call(mc);
return mc;
};
var setDimentions = function () {
if ((this._width += step)>=this.max_width) {
this._width = this.max_width;
}
if ((this._height += step)>=this.max_height) {
this._height = this.max_height;
}
if (this._height == this.max_height && this._width == this.max_width) {
delete this.onEnterFrame;
}
};
tmp.init = function() {
this._width = this._height=1;
this.onEnterFrame = setDimentions;
};
};
this.setBallClass();
#endinitclip

- я понимаю, дураков нет корячиться - набивать самому, если можно просто скопировать. Но совесть-то надо иметь. Если просят модифицировать ручками, надо модифицировать ручками, а не копировать.

Ок. бросаем на сцену мувик ball_mc и слоем выше втыкаем код:

Ball.create(this, "ball1_mc", 1, {_x:30, _y:50})

Тестируем, не верим своим глазам, восхищаемся результатом.
Мы создали статический метод, create, который нам может быть очень полезным, в случае, когда предполагается, что данный класс может быть загружен извне динамически, а объект класса будет использоваться в другом ролике. В этом случае приаттачить мувик не удастся и нас спасет статический метод create.
Параллельно предлагаю маленький тренинг:
Выяснилось, что нам не нужны max_width и max_height в прототипе класса и Билл Гейтс дал указание сделать эти переменные приватными. Время пошло.

Ну как? Успели в отведенное время? За это вам повышена зарплата на $6000.
Не стоит благодарности.

Что еще? или подводные камни
Иногда, а скорее достаточно часто, требуется, чтобы класс, однажды инициализированный, не инициализировался заново.
Запросто. Просто добавим проверку на наличие класса:

#initclip
this.setBallClass = function() {
delete this.setBallClass;
if (_global.Ball) {
return
}
// код дальше....
};

второй момент, на который бы мне хотелось указать, это то, что локальные переменные можно объявлять когда угодно, т.е. использовать до того, как они объявлены. В связи с этим не рекомендуется (особенно в отношении конструктора класса) практика задания функций таким способом:

function myPrivateFunction (){
// code here
}

и настоятельно рекомендуется использование такого синтаксиса:

myPrivateFunction = function (){
// code here
}

Хотя эффект практически тот же, однако, в первом случае, компилятор во время компиляции перемещает функцию со своего места на самый верх. В итоге функция будет объявлена до кода:

if (_global.Ball) {
return
}