Статьи ActionScript
Нарядная инициализация 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[i] = initObj[i];
    }
    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
}
// код дальше....
};<.code>
второй момент, на который бы мне хотелось указать, это то, что локальные переменные можно объявлять когда угодно, т.е. использовать до того, как они объявлены. В связи с этим не рекомендуется (особенно в отношении конструктора класса) практика задания функций таким способом:
function myPrivateFunction (){
// code here
}
и настоятельно рекомендуется использование такого синтаксиса:
myPrivateFunction = function (){
// code here
}
Хотя эффект практически тот же, однако, в первом случае, компилятор во время компиляции перемещает функцию со своего места на самый верх. В итоге функция будет объявлена до кода:
if (_global.Ball) { 
 return 
}
что в некоторых случаях может повлечь за собой неожиданные последствия.

Статья опубликована на сайте flashzone.ru.