Ответ на вопрос, вынесенный в заголовок этого параграфа, прост и сложен одновременно.
Разумеется, язык ActionScript был разработан фирмой Macromedia,
выпускающей Флэш. Однако разработан не на пустом месте, а на основе
стандарта ECMA-262.
Сначала скажем, как расшифровывается
аббревиатура ECMA: это European Computer Manufacturers Association
(Европейская ассоциация производителей компьютеров). Эта ассоциация
(разумеется, не она одна) занимается стандартами в области вычислительной
техники.
ActionScript, как и другой известный язык
JavaScript, базируется на стандарте ECMA-262.
(Фактически, стандарт был сделан на основе начальных версий
JavaScript, а не наоборот, а вот ActionScript уже был
сделан на основе стандарта. Конечно, само существование стандарта на
развитие языка JavaScript также повлияло). Стандарт разработан
специально для скриптовых языков, поэтому не специфицирует полностью все
встроенные объекты. То есть для JavaScript встроенными объектами
являются браузер и его окна, для ActionScript - клипы, кнопки,
текстовые поля и компоненты Flash. Сам стандарт ECMA-262 имеет
несколько версий, самая свежая, (к которой и имеет непосредственное
отношение ActionScript 1.0) - это версия 3. (Вообще-то готовится
версия 4, и на основе ее бета-версии уже сделан язык ActionScript
2.0, используемый во Флэш МХ 2004. Но его мы не будем рассматривать в этой
книге). Надо отметить, что стандарт в ActionScript поддержан
все-таки не полностью. Не реализована обработка исключений и регулярных
выражений, ограничена функциональность оператора eval (об этом см. в
лекции 3). Тем не менее, ActionScript соответствует стандарту в
значительной степени.
Если вы хотите ознакомиться со стандартом
ECMAScript, он же ECMA-262, вы можете это сделать на сайте ECMA
(http://www.ecma-international.org/, конкретный адрес http://www.ecma-international.org/publications/standards/Ecma-262.htm
), или на сайте Mozilla в разделе, посвященном языку JavaScript
(http://www.mozilla.org/js/language/ , здесь вы сможете
найти стандарт в формате Microsoft Word).
Еще раз скажем, что последней
версией стандарта является третья; вторая версия в свое время была
утверждена в качестве стандарта ISO/IEC 16262, однако в настоящее время
ISO уже отозвала этот стандарт.
Также отметим, что большинство
нововведений, которые были сделаны в третьей версии стандарта (такие, как
регулярные выражения, обработка исключений и т.п.), не вошли в
ActionScript.
Вашу работу на ActionScript значительно
облегчит то, что большинство простых конструкций, которые сработают на С++
или Java, сработают и на ActionScript. Так что, если перед вами
стоит задача написать кусочек кода в 50 строк - попробуйте написать код,
который бы заработал и в C++ и в Java (учтите, что объявлять переменные
заранее не нужно). С довольно большой вероятностью вам удастся заставить
вашу программу вести себя так, как вы того хотите. Существенный минус
такого способа (помимо того, что он не годится для программ более 100
строк) состоит в том, что остается ощущение некачественно сделанной
работы. Профессионал-программист обычно не только хочет, чтобы его
программа работала, он еще и хочет понимать, почему именно она работает.
Так что давайте вникать в детали - начиная с самых базовых вещей. Но при
этом мы все же будем считать, что вы понимаете общий смысл кода на
ActionScript - даже если некоторые применяемые там вещи мы еще не
обсуждали. Для профессионала С++ или Java такое предположение будет, без
сомнения, верным. Если же вы, по каким-либо причинам, используете эту
книгу, впервые изучая язык с С-подобным синтаксисом - запаситесь еще одним
учебником, в котором этот синтаксис рассмотрен более подробно. (В любом
случае вам не помешает какая-нибудь толстая книжка по Флэш-дизайну из тех,
что указаны в списке литературы.)
Но прежде чем мы погрузимся в детали,
упомянем пару удивительных и непривычных вещей, с которыми нам придется
столкнуться. Первая из них такова: точки с запятой после операторов во
Флэше ставить необязательно! (Мы не будем пользоваться этой "свободой",
она от лукавого.) Вторая удивительная вещь (впрочем, в настоящее время она
уже не так удивительна) - это полная поддержка Unicode, в том числе - для
идентификаторов языка. То есть, если вам хочется, то ваши классы, объекты,
их поля и методы вы можете называть по-русски и русскими
буквами.
a = (5 < 4).toString(); // В переменную записывается слово false trace("a.length = " + a.length); trace("9+6..toString() = " + 9+6..toString()); trace("строка, набранная строчными буквами".toUpperCase());
Этот код выводит в консоль:
a.length = 5 9+6..toString() = 96 СТРОКА, НАБРАННАЯ СТРОЧНЫМИ БУКВАМИ
Итак, мы видим, что и булевское, и числовое, и строчное
выражение допускают вызов какого-либо метода для примитивного
объекта, являющегося значением выражения. Главное - позаботиться о
синтаксисе. Если выражение брать в круглые скобки - никаких проблем не
возникнет. А вот в случае числового выражения 6 во втором операторе вывода
в консоль нам пришлось поставить две точки перед именем метода
(хотя можно было выкрутиться и при помощи скобок). Если бы мы поставили
одну точку после цифры 6, это означало бы, что за ней следует некоторое
количество (возможно, ноль) десятичных знаков, но никак не сразу имя
метода. Поэтому пришлось ставить две точки, и это уже
воспринимается однозначно. Обратите также внимание, что в том же самом
выражении к преобразованной в строку шестерке была прибавлена числовая
девятка. Тем не менее, она тоже была преобразована в строку. При неявном
преобразовании типов во Флэше преобразование в строку имеет
приоритет - оно всегда применяется, если один из операндов
строковый и полученное выражение будет иметь смысл очевидных операций со
строками. Впрочем, о преобразованиях типов мы поговорим
далее.
Для порядка проверим, что все, что сказано в этом подпараграфе
про скобки, также работает. В самом деле, запустив
код:
trace(4+(7).toString()); trace(4+(7));
мы на выходе получим:
47 11
так что синтаксис со скобками работает, как ожидалось.
Объекты во Флэш МХ и похожи, и непохожи на объекты в Java. Общим свойством является то,
что все объекты во Флэш доступны только по ссылке. Таким образом,
создавая объект любым способом, мы получаем доступ лишь к ссылке на
него. Сам объект хранится отдельно и напрямую недоступен. Например,
мы пишем
a = new Object();
Теперь в а хранится ссылка на
этот объект. Если вы специалист в С++, но не знаете Java, то
обратите внимание, что оператор new вернул не указатель (в Java нет
указателей), а ссылку. И к полю по имени i объекта a можно
обратиться как обычно, через точку:
a.i = 5;
Правда, мы не
предпринимали никаких усилий для того, чтобы создать в объекте a
поле i. Но во Флэше это необязательно! Как мы увидим далее, поле
создастся в таком случае автоматически (что очень плохо для отладки,
но хорошо для расширения возможностей Флэш МХ). Кстати, поле i в
объекте а теперь является ссылкой на read-only
объект, представляющий собой число 5. Кроме ссылок, в объектах Флэш
не хранится ничего. Даже методы - и те являются ссылками на
объекты-функции (подробнее об этом - далее). Сами же объекты
хранятся отдельно и иначе как через ссылки нам недоступны. А
объекты, на которые никто не ссылается, удаляет сборщик мусора.
Детали работы этого сборщика остаются тайной. И подстегнуть его
работу вручную, как это делается в Java, во Флэше, к сожалению,
нельзя.
Есть специальное ключевое слово, позволяющее сделать
ссылку, ведущую в пустоту. Это слово null. Присваивают null
какой-либо ссылке в том случае, когда не хотят ее удалять совсем, но
при этом желают явно обозначить, что пока что ссылка никуда не
ведет. Но можно и удалить ссылку с помощью ключевого слова delete.
Запомните, что во Флэше delete не удаляет объект, на который
указывает ссылка. Он просто удаляет саму ссылку (тем самым,
освобождая некоторое количество памяти). А объект будет удален
сборщиком мусора, если на него больше никто не указывает. Когда
настанет время.
Вот взгляните на такой пример:
a = {f: 4, g:
10}; // Создает объект а с полями f = 4 и g =
10
trace(a.g);
delete a.g;
trace(a.g);
После запуска
этот код выводит в консоль
10
undefined
Так что
удаление прошло успешно. Обратите внимание на способ создания
объекта с заранее заданными значениями ряда полей. В качестве одного
из полей можно задать еще один объект, созданный прямо на месте,
например вот так:
a = {f: 4, g: 10, k: {e: 50, r:
60}};
Можно даже вызвать функцию у такого созданного
"впопыхах" объекта:
a = {f: 4, g: 10, k: {e: 50, r:
60}.toString()};
trace("a.k = " + a.k);
Запускаем
(Ctrl+Enter, как обычно) и получаем в консоли
a.k = [object
Object]
Значение [object Object] - это то, что функция
toString() выводит по умолчанию. Для вашего собственного объекта вы
сможете ее переопределить (см. параграф "Функции и
методы").
Запомните, что во Флэш МХ нет объектов, которые
"висят в воздухе", то есть на которые нет ссылок в каком-то другом
объекте. Они удаляются сборщиком мусора. Есть ряд системных
объектов: это _global, в котором хранятся глобальные данные, и
объекты с именами _level0, _level1 и т.д., которые представляют
собой ссылки на текущие загруженные флэш-ролики (каждый из которых
подгружен из отдельного *.swf-файла). Внутри кода каждого из роликов
можно получить ссылку на корневой объект ролика с помощью записи
_root. Еще есть системные объекты, соответствующие контекстам вызова
функций (о них чуть позже). Если мы будем прослеживать цепочку
"владения", то убедимся, что она непременно восходит к одному из
вышеописанных системных объектов. То есть, если бы мы имели
возможность удалить все эти объекты, то сборщик мусора удалил бы все
остальные объекты текущей флэш-программы. Впрочем, объекты,
начинающиеся на _level действительно можно выгружать, но это
отдельный разговор.
Касается ли сказанное выше примитивных
типов? Да, ведь они тоже объекты, только
read-only. А локальных переменных? Снова ответ
"да". Локальные переменные хранятся в так называемом объекте
активации функции (activation object), или, как мы
обычно будем про него говорить, контексте вызова функции. Далее мы
научимся даже получать прямую ссылку на этот объект.
Возникает
естественный вопрос: а где лежали переменные из примеров,
приведенных выше? Ведь мы создавали их, определяя Action для
какого-то кадра линейки времени? Так вот, линейка времени всегда
принадлежит какому-либо клипу. Именно в соответствующем ему объекте
(типа MovieClip) и лежат переменные, созданные в Actions. В нашем
случае мы вовсе не создавали никакого нового клипа. Так что все
переменные попали в корневой клип. Он называется _root. Из любого
клипа мы могли бы обратиться к ним, например, так:
_root.a.
b = Примитивная строка
b.x = undefined
(b instanceof
String) = false
Итак, оба способа демонстрируют разницу между
объектными и примитивными типами. Несмотря на эту
разницу, методы объектной и примитивной строки полностью совпадают.
Это неспроста: на самом деле примитивные строки - это действительно
объекты типа String, только read-only, и то, что
instanceof умудряется заметить разницу - само по себе
неожиданно.
Заметим также, что в приведенном выше примере мы
могли проверять с помощью instanceof на принадлежность к типу
Object: писать trace("(b instanceof Object) = " + (b instanceof
Object));. В этом случае мы могли бы сразу проверить на
примитивность не только строку, а вообще какой угодно тип. Скажем,
такая проверка сообщит нам, что функции являются объектами. Правда,
мы еще ни слова не сказали о том, как во Флэше определять свои
функции. К этому мы сейчас и перейдем.
В этом параграфе мы будем говорить о функциях очень кратко и приведем лишь пару примеров. Работа с
функциями во Флэше богата интереснейшими деталями, и все они
будут весьма подробно разобраны в лекции 5. При желании вы
можете сразу перепрыгнуть к этой лекции и просмотреть ее
первые параграфы, а затем вернуться сюда.
Пример глобальной
функции
Если вы хотите определить функцию в глобальной
области видимости, вам следует написать вот так
// Определяем глобальную функцию _global.printSomeStringWithFrame = function(str){ trace("***************************"); trace(str); trace("***************************"); } // Проверяем, как она работает printSomeStringWithFrame("Строка в рамке");На выходе получаем:
*************************** Строка в рамке ***************************то есть функция сработала. Еще раз отметим, что функция, определенная таким образом (как принадлежащая объекту _global) будет видна в любом объекте или клипе без явного указания _global.
Без чего невозможна нормальная работа программиста - так это без четкого
представления деталей работы числовых типов. Во Флэше числовой
тип только один, это Number. Тем больше у нас оснований
разобраться с ним как можно тщательнее.
Тип Number:
точность
Итак, каково же внутреннее устройство типа Number
во Флэше? В принципе, вы можете посмотреть в стандарт
ECMAScript и сразу же обнаружить, что под типом Number
скрывается. Однако мы поступим по-другому: мы сначала
постараемся выяснить все что можно средствами самого Флэш МХ.
А уж потом, когда все станет ясно, расставим последние точки.
Почти наверняка те, на первый взгляд, хорошо знакомые вам
вещи, которые мы обнаружим, засверкают новыми
гранями.
Итак, в тип Number мы можем записать любые
числовые значения: как целые, так и с плавающей точкой.
Слишком большие целые константы (или константы с фиксированной
точкой), встречающиеся в коде, преобразуются в значения с
плавающей точкой (делает это, по-видимому, компилятор). На
самом деле, внутреннее представление Number - это именно число
с плавающей точкой. Но при вызове метода toString() (например,
при выводе числа в консоль) производится проверка: если можно
вывести число в форме целого или в форме числа с фиксированной
точкой - это будет сделано. Возможность вывода в виде целого
числа или числа с фиксированной точкой ограничивается
точностью мантиссы во внутреннем представлении Number. Ведь
число значащих цифр в любом представлении числа не должно
превосходить число цифр в мантиссе. А еще точнее - число
значащих битов не должно превосходить число битов в
мантиссе.
Сейчас мы произведем ряд экспериментов, из
которых выясним, что точность мантиссы - 53 бита. В 53 бита
помещается 15 знаков мантиссы (14 знаков после запятой).
Бывает, что число выводится с меньшим количеством знаков - но
это лишь потому, что замыкающие нули не показываются для чисел
в экспоненциальной записи. Так что на самом деле точность
составляет 14 знаков после запятой всегда. Вообще-то мантисса
с 14 знаками после запятой помещается (порой с хорошим
запасом) не то что в 53, а даже и в 50 бит. И остается еще как
минимум 3 дополнительных бита, которые позволяют увеличить
точность расчетов и сделать последнюю цифру числа более
надежной (при выводе числа мы все равно видим не более 14 цифр
после запятой, так что "запасные" биты используются для
округления). Итак, вот те примеры, которые демонстрируют
сказанное выше.
// Сколько знаков дают 50 бит: все целые
числа вплоть до 10^15
// (все 15-значные числа) помещаются
в 50 бит
// (числа вплоть до 2^50 - 1)
trace("2^50 - 1 =
" + (Math.pow(2, 50) - 1));
// Сколько знаков дают 53 бита:
// НЕ ВСЕ целые
числа вплоть до 10^16 помещаются в 53 бита
// (числа вплодь
до 2^53 - 1), так что 53 битам
// все равно соответствует
15 знаков (14 после запятой).
trace("2^53 - 1 = " +
(Math.pow(2, 53) - 1) + "\n");
// Демонстрируем, что "лишние биты"
присутствуют
// и повышают точность вычислений
for (i=0;
i<=10; i++){
x = Math.pow(2, 52) - 2;
y = Math.pow(2,
52) - 1 - i;
trace("x = " + x + "; y = " + y + "; x - y = "
+ (x - y));
}
// Просто печатает пустую строку для
лучшего
// форматирования выходных данных
trace("");
// Демонстрируем, что внутреннее представление
мантиссы
// действительно записывается в 53 бита (не считая
знак).
for (i=0; i<=10; i++){
// Число 2^(52+i)
помещается в 53+i бит,
// так что последние i бит
отрезаются
a = Math.pow(2, 52+i);
// Прибавляем двоичное
число 10101010101
// (единицы чередуются с нулями, чтобы
избежать
// округления при обрезании последних битов
b =
a + parseInt("10101010101", 2);
// Выводим разницу
полученной суммой и а
// в двоичном виде (аргумент функции
Number.toString -
// это основание системы
счисления).
trace("Bits: " + (53 + i) + "; b - a = " + (b -
a).toString(2));
}
// Проверяем, во скольких разрядах происходит
преобразование
// в двоичную систему
trace(
"\n(Math.pow(2, 31)).toString(2) = "
+
(Math.pow(2,
31)).toString(2)
);
trace(
"(Math.pow(2, 31) +
1).toString(2) = " +
(Math.pow(2, 31) +
1).toString(2)
);
// И в 36-ричную
trace(
"(Math.pow(2, 31)).toString(36) = "
+
(Math.pow(2,
31)).toString(36)
);
trace(
"(Math.pow(2, 31) +
1).toString(36) = " +
(Math.pow(2, 31) +
1).toString(36)
);
После запуска этого кода мы
получим:
2^50 - 1 = 1.12589990684262e+015
2^53 - 1 =
9.00719925474099e+015
x = 4.50359962737049e+15; y = 4.5035996273705e+15; x - y =
-1
x = 4.50359962737049e+15; y = 4.50359962737049e+15; x -
y = 0
x = 4.50359962737049e+15; y = 4.50359962737049e+15; x
- y = 1
x = 4.50359962737049e+15; y = 4.50359962737049e+15;
x - y = 2
x = 4.50359962737049e+15; y =
4.50359962737049e+15; x - y = 3
x = 4.50359962737049e+15; y
= 4.50359962737049e+15; x - y = 4
x = 4.50359962737049e+15;
y = 4.50359962737049e+15; x - y = 5
x =
4.50359962737049e+15; y = 4.50359962737049e+15; x - y = 6
x
= 4.50359962737049e+15; y = 4.50359962737049e+15; x - y =
7
x = 4.50359962737049e+15; y = 4.50359962737049e+15; x - y
= 8
x = 4.50359962737049e+15; y = 4.50359962737048e+15; x -
y = 9
Bits: 53; b - a = 10101010101
Bits: 54; b - a =
10101010100
Bits: 55; b - a = 10101010100
Bits: 56; b -
a = 10101011000
Bits: 57; b - a = 10101010000
Bits: 58;
b - a = 10101100000
Bits: 59; b - a = 10101000000
Bits:
60; b - a = 10110000000
Bits: 61; b - a =
10100000000
Bits: 62; b - a = 11000000000
Bits: 63; b -
a = 10000000000
(Math.pow(2, 31)).toString(2) = -
(Math.pow(2, 31) +
1).toString(2) =
-
111111111111111111111111111111
(Math.pow(2,
31)).toString(36) = -
(Math.pow(2, 31) + 1).toString(36) =
-zik0zj
Давайте подробно разберем, что же означает весь
этот вывод нашей программки. Две первых строки показывают, что
15 знаков помещаются в 50 бит, но 16 не поместятся и в 53.
Затем идут строчки (вычисление значения выражения х - у для
разных у), которые демонстрируют, что дополнительные биты
действительно есть. Во всех строчках этой серии (кроме первой
и последней) выводимые значения х и у одинаковы. Но разность
их отнюдь не нулевая; нужные нам биты мы не потеряли.
Заметьте, что здесь есть и некоторый подвох: не всегда два
числа, которые выглядят одинаково при выводе на печать,
являются равными (ненулевая разность означает, что x == y дает
false). Впрочем, это обычное дело при сравнении чисел с
плавающей точкой; достаточно лишь соблюдать осторожность и
писать вместо х == у что-то вроде Math.abs(x - y) <
(Math.abs(x)+Math.abs(y))*1e-13. Если же ваши данные являются
целыми, можете смело их сравнивать с помощью оператора
"==".
Далее следует около десятка строчек, которые
иллюстрируют разрядность внутреннего представления мантиссы
(без учета знака). Если в двоичном представлении числа имеется
53 знака, то младшие биты не обрезаются. Легко видеть, что для
54-битного числа обрезается самый младший бит, для 55-значного
- два бита и т.д. Наконец, четыре последние строки возникли, в
частности, из попытки получить информацию о содержимом
"запасных" битов напрямую. Вдруг вывод числа в двоичной
системе (или другой системе счисления) поможет и эти биты
станут видны? Оказалось, однако, что при преобразовании в
другую систему счисления используется 32-битный регистр, и не
то что о 53, но и о 50 битах не может быть и речи. Причем это
обыкновенный дополнительный код, то есть 32-й бит кодирует
знак. (Именно поэтому такой странный результат получается при
попытке вывести 231 - воспринимаемое как дополнительный код,
такое число не имеет смысла). То же касается и 36-разрядной
системы (как и любой другой, кроме десятичной) - 231 + 1
воспринимается как отрицательное число. Почему для примера мы
взяли именно 36-ричную систему? Потому что это система
счисления с наибольшим основанием - из тех, в которые функция
toString способна переводить числа. Почему именно 36? А просто
это 10 (число цифр) плюс 26 (число букв латинского алфавита).
Для еще больших оснований знаков просто не хватает. И если вы
попытаетесь ввести большее число в качестве аргумента
toString, такой аргумент будет попросту проигнорирован (и вы
получите десятичную запись). Наконец, укажем еще на то, что
при переводе в другую систему счисления само число и основание
системы приводятся к целым (причем не округлением, а
отбрасыванием дробной части).
Итак, разрядность мантиссы мы определили. Неплохо
было бы прикинуть теперь разрядность порядка. Чтобы это
сделать, нам необходимо знать максимальное и минимальное по
модулю значения, которые можно записать в типе Number.
Эти
значения легко получить, поскольку они хранятся в специальных
константах Number.MAX_VALUE и Number.MIN_VALUE. Так вот,
Number.MAX_VALUE равно 1.79769313486231e+308, а
Number.MIN_VALUE равно 4.94065645841247e-324. Почему
минимальное значение имеет порядок больший по абсолютной
величине (минус 324, а не минус 308)? Дело в том, что мы
можем, сохраняя порядок, уменьшать мантиссу до весьма малых
значений (гораздо меньше 1). В обычных случаях это не
практикуется, но очень малые по абсолютной величине числа
позволяет записывать.
Давайте подсчитаем, сколько битов
нужно для хранения порядка числа. Для этого надо в первую
очередь вспомнить, что компьютеру, разумеется, удобнее
оперировать числом с плавающей точкой в форме , а вовсе не
(здесь m - мантисса, а n - порядок). 10308 - это примерно
21023 (ведь 308*log210 = 1023.15). Используя 11 битов, мы
сможем записать положительные и отрицательные значения
величиной от 0 до 1023. Итак, на хранение порядка надо 11 бит,
а мантисса занимает 53 бита, как мы уже выяснили. Еще один бит
нужен на знак числа. Всего получается 65; еще бы чуть-чуть
сэкономить - и выйдет удобные 64 бита. И действительно,
сэкономить есть на чем. Поскольку число у нас представляется в
виде , то мантисса может быть не от 1.0 до 10.0 (точнее,
строго меньше 10.0), как в обычном десятичном представлении, а
от 1.0 до 2.0 (строго меньше 2.0). В результате первый бит
мантиссы - всегда единица, так что его можно не хранить. Итого
на внутреннее представление Number должно отводиться 64 бита
(о чем вы, наверняка, догадались с самого начала; но надо ведь
было поддержать видимость сюжетной интриги).
А теперь,
когда интрига закончилась, пора срывать покровы. Конечно,
внутри Number на самом деле скрывается тип double. Самый
настоящий, соответствующий стандарту IEEE 754. Впрочем, было
бы странно, если бы разработчики Флэш пренебрегли такой
удобной и стандартной вещью. И, оказывается, хранить в double
целые числа тоже весьма удобно. Особенно удобно то, что часто
после ряда арифметических операций, имевших в качестве
промежуточных результатов нецелые числа, в окончательном
ответе мы можем получить целое число, причем абсолютно точно.
Лучше всего срабатывает округление после операций умножения
или деления. Вот примеры
a = 1/3;
trace("a = 1/3, то
есть " + a);
trace("(a*3 == 1) = " + (a*3 ==
1))
trace("(a*3 - 1)*Math.pow(2, 52) = " + (a*3 -
1)*Math.pow(2, 52));
trace("(a -
0.333333333333333)*Math.pow(2, 52) = "
+ (a -
0.333333333333333)*Math.pow(2, 52));
trace("(a -
0.3333333333333333)*Math.pow(2, 52) = "
+ (a -
0.3333333333333333)*Math.pow(2, 52));
trace("");
//
Пробуем разделить 1 на другое число
b = 1/Math.pow(7,
45);
trace("b = 1/Math.pow(7, 45), то есть " +
b);
trace("(b*Math.pow(7, 45) == 1) = " + (b*Math.pow(7,
45) == 1))
На выходе имеем:
a = 1/3, то есть
0.333333333333333
(a*3 == 1) = true
(a*3 -
1)*Math.pow(2, 52) = 0
(a - 0.333333333333333)*Math.pow(2,
52) = 1.5
(a - 0.3333333333333333)*Math.pow(2, 52) = 0
b
= 1/Math.pow(7, 45), то есть
9.34519137233795e-39
(b*Math.pow(7, 45) == 1) =
true
А вот после других операций может не так повезти.
Попробуем, скажем, вычислить (101/3)3. Должно получиться опять
10. А на деле получаем 9.99999999999999. А может быть еще
хуже: (101/5)5 на печати выдает 10, но при попытке сравнить
полученное число с десяткой, мы получаем false. Посмотрите
сами: код
trace(Math.pow(Math.pow(10, 1/3),
3));
trace(Math.pow(Math.pow(10, 1/5),
5));
trace(Math.pow(Math.pow(10, 1/5), 5) ==
10);
дает на
выходе
9.99999999999999
10
false
Так что при
сложных вычислениях с плавающей точкой никто не избавит нас от
необходимости сравнивать числа приблизительно так: Math.abs(x
- y) < (Math.abs(x)+ Math.abs(y))*1e-13. Мы уже говорили об
этом чуть раньше, но привычка использовать такой прием при
сравнении чисел с плавающей точкой настолько важна, что
повториться не помешает.
Мы посвятили довольно много времени разбору представления чисел в типе Number. Однако в этом типе могут храниться не только числовые, а точнее - не совсем числовые значения. Всего этих специальных значений имеется три. Два из них служат для обозначения бесконечности. Причем бесконечность бывает разная: положительная и отрицательная. В этом есть некоторый смысл, поскольку положительная бесконечность всего лишь обозначает любое число, большее, чем Number.MAX_VALUE. А отрицательная - число, меньшее чем (-Number.MAX_VALUE). Хотя при делении на ноль есть некоторый произвол - какую именно из бесконечностей употребить. А вот при попытке вычислить логарифм нуля мы всегда получим именно минус бесконечность, что естественно. Так вот, положительной бесконечности соответствует константа Number.POSITIVE_INFINITY. А отрицательной, соответственно - константа Number.NEGATIVE_INFINITY. При преобразовании в строку эти константы обращаются в Infinity и -Infinity. Вот парочка примеров с использованием этих значений:
trace("5/0 = " + 5/0);
trace("-5/0 = " + -5/0);
trace("-5/-0 = " + -5/-0);
trace("-5/Number.MIN_VALUE = " + -5/Number.MIN_VALUE);
trace("-5/-Number.MIN_VALUE = " + -5/-Number.MIN_VALUE);
trace("Math.log(0) = " + Math.log(0));
trace("Math.sqrt(-1) = " + Math.sqrt(-1));
На выходе получаем:
5/0 = Infinity
-5/0 = -Infinity
-5/-0 = -Infinity
-5/Number.MIN_VALUE = -Infinity
-5/-Number.MIN_VALUE = Infinity
Math.log(0) = -Infinity
Math.sqrt(-1) = NaN
Видим, что попытка обнаружить разницу между 0 и (-0) провалилась
- выражение (-0) тут же вычисляется и получает значение, равное 0. А
вот если вместо 0 использовать Number.MIN_VALUE (наименьшее по
абсолютной величине число, которое отличается от нуля) - тут уже
разница между Number.MIN_VALUE и (- Number.MIN_VALUE) отчетливо
видна.
Но вернемся к нечисловым значениям Number. Последнее из
них (с которым, тем не менее, довольно часто приходится иметь дело)
- это Number.NaN (при преобразовании в строку переводится в "NaN").
Название это является сокращением от "Not a Number"
- "не число". Употребляется NaN для значений выражений, которые
невозможно представить в виде действительного (хотя бы и
бесконечного) числа. Пример мы только что видели - значением
выражения Math.sqrt(-1) является Number.NaN. Кроме того, выражения c
арифметическими операторами, подразумевающими преобразование к
Number, в которые попали типы данных, не работающие с подобными
операторами, также приобретают значение Number.NaN. Примером типов,
приобретающих значение Number.NaN при преобразовании к Number,
являются, например, String или Function. Заметьте, что в одном из
предыдущих примеров в выражениях, наподобие trace("x = " + x + "; y
= " + y + "; x - y = " + (x - y)) арифметические вычисления взяты в
скобки. Если скобки убрать, значением выражения станет Number.NaN,
поскольку вся начальная часть выражения преобразуется в строку, а
затем эта строка будет приведена к типу Number при попытке вычесть
из нее число y.
Кстати, писать именно Number.NaN необязательно.
Можно употреблять и просто NaN. Также существует и предопределенная
переменная Infinity (хотя редактор ActionScript среды Флэш МХ ее не
узнает и не подсвечивает, но пользоваться ей можно и означает она то
же самое, что и Number.POSITIVE_INFINITY). Можем ли мы проверить,
что Number.NaN и NaN - это действительно одно и то же? Ведь оператор
== не работает со значением NaN (всегда выдает false). Для подобной
проверки есть функция isNaN. Сейчас мы продемонстрируем ее работу (и
работу в чем-то родственной функции isFinite). Вот такой код
trace("NaN на печати выглядит как " + NaN);
trace("isNaN(NaN) = " + isNaN(NaN));
trace("Infinity на печати выглядит как " + Infinity);
trace("-Infinity на печати выглядит как " + (-Infinity));
trace("Number.NEGATIVE_INFINITY на печати дает "
+ Number.NEGATIVE_INFINITY);
trace("-Number.NEGATIVE_INFINITY на печати дает "
+ -Number.NEGATIVE_INFINITY);
trace("isFinite(Infinity) = " + isFinite(Infinity));
trace("1/Infinity = " + 1/Infinity);
выводит
NaN на печати выглядит как NaN
isNaN(NaN) = true
Infinity на печати выглядит как Infinity
-Infinity на печати выглядит как -Infinity
Number.NEGATIVE_INFINITY на печати дает -Infinity
-Number.NEGATIVE_INFINITY на печати дает Infinity
isFinite(Infinity) = false
1/Infinity = 0
Несмотря на то что ссылки во Флэш МХ не типизированы (то есть одна и та же ссылка
может указывать на объекты разных типов), работа с типами
здесь поддержана неплохо. Мы уже говорили о том, как
определить тип объекта. Кроме того, есть средства явного и
неявного преобразования типов. Сначала поговорим о
неявном преобразовании. Мы рассмотрим здесь наиболее
важные случаи неявного преобразования типов.
Некоторые дополнительные сведения (например, преобразования
строк при сравнении) будут рассмотрены в параграфе,
посвященном выражениям.
Во Флэш МХ есть два специальных типа, для которых значение объекта однозначно задается его типом.
Этот тип имеет значение null, которое специально используется для ссылок в пустоту. Как мы уже говорили, во Флэше можно было бы обходиться и без этого типа. Зато наличие ключевого слова null упрощает перенос на Флэш МХ фрагментов Java-кода.
undefined - это еще один специальный тип. Такой тип имеет
выражение, обращающееся к переменной (объекту, функции), значение
которой не было заранее задано. Впрочем, если undefined встречается
в выражении, в котором оно должно быть преобразовано в строку - оно
превращается в пустую строку. При преобразовании в число получается
0, в булевский тип - false. Если переменная была заведена, а потом
была удалена с помощью delete, то справившись о значении этой
переменной мы снова получим undefined. Запомните, что при сравнении
null и undefined операторами == или != мы не сможем обнаружить
разницы между двумя этими значениями. Но то, что эти значения разных
типов, помогает нам решить проблему при помощи использования
операторов "строгого равенства - неравенства" === и !==, специфичных
для Флэш МХ. Об этих операторах мы еще будем говорить далее в
параграфе, посвященном выражениям.
Таблица 2.1. Приведение типа значения typeof | |
Тип |
typeof возвращает |
Number |
number |
Boolean |
boolean |
String |
string |
Object |
object |
Function |
function |
Array |
array |
MovieClip |
movieclip |
Button |
object |
Text field |
object |
undefined |
undefined |
null |
null |
Давайте теперь пронаблюдаем работу этого оператора на примерах.
Пишем следующий код:
trace('typeof(5 + "") = ' + typeof(5 + ""));
trace('typeof(a) = ' + typeof(a));
a = new Object();
trace('typeof(a) = ' + typeof(a));
a = null;
trace('typeof(a) = ' + typeof(a));
a = {x: 20, y: "строчка"};
trace('typeof(a) = ' + typeof(a));
trace('typeof(a.x) = ' + typeof(a.x));
trace("typeof(String) = " + typeof(String));
trace("typeof(Function) = " + typeof(Function));
// Запись typeof(function) выдаст синтаксическую ошибку
На выходе получаем:
typeof(5 + "") = string
typeof(a) = undefined
typeof(a) = object
typeof(a) = null
typeof(a) = object
typeof(a.x) = number
typeof(String) = function
typeof(Function) = function
Все результаты, кроме двух последних, говорят сами за себя (заметим только, что мы
уже не первый раз используем сложение чисел и строчек; подробнее о том, как это работает, будет написано в следующем параграфе, а
детали работы со строчками разобраны в четвертой лекции). Последние же два результата явно требуют комментариев. Впрочем, с подробным
объяснением нам придется подождать до шестой лекции, в которой пойдет речь о классах во Флэш МХ. Пока же нам следует сказать, что
во Флэше конструктор класса хранит в себе ссылку на полную информацию об этом классе. И, таким образом, когда мы хотим иметь
дело с классом, а не с объектом, мы должны иметь дело с конструктором. Так вот, String - это имя конструктора класса String,
а Function - имя конструктора класса Function, к которому принадлежат все функции во Флэше.
Таблица 2.2. Предопределенные суффиксы и соответствующие им типы | ||
Тип |
Суффикс |
Примечание |
Обыкновенные объекты | ||
Array |
_array |
|
Button |
_btn |
Стандартная кнопка (не компонент) |
Color |
_color |
|
Date |
_date |
|
Sound |
_sound |
Именно объект типа Sound, а не просто звук, импортированный в клип |
String |
_str |
|
TextField |
_txt |
|
TextFormat |
_fmt |
|
XML |
_xml |
|
XMLSocket |
_xmlsocket |
|
Компоненты | ||
FCheckBox |
_ch |
|
FComboBox |
_cb |
Компонент "Выпадающий список" |
FListBox |
_lb |
|
FPushButton |
_pb |
Кнопка-компонент |
FRadioButton |
_rb |
|
FScrollBar |
_sb |
"Область с прокруткой" и (многострочные) текстовые поля. |
FScrollPane |
_sp |
Компонент "Область с прокруткой" |
Кроме этих суффиксов, Флэш МХ реагирует еще на несколько предопределенных имен, про которые ему заранее известно, что это объекты типа MovieClip. А именно, на _root (корневой клип данного флэш-ролика), _parent (в объектах типа MovieClip так называется поле, ссылающееся на родительский клип) и, наконец, на _level0, _level1, _level2 и т.д. (корневые клипы различных флэш-роликов, загруженных в плеер в настоящий момент). В лекции 12 мы расскажем вам о том, как самим делать подобные шаблоны, на которые реагирует среда разработки Флэш МХ.
Флэш МХ имеет набор операторов, слегка расширенный по сравнению с Java. Сначала мы
опишем (а точнее, кратко перечислим) ту часть операторов,
которая является общей для Флэш МХ, Java и С++. Затем
подробнее рассмотрим операторы, которых в С++ нет но есть в
Java (для тех читателей, которые не работали на Java). И,
наконец, подробно расскажем про операторы, которые вы кроме
ActionScript (и JavaScript) нигде не
найдете.
trace("a() & b() = " + (a() &
b()));
trace("----------");
trace("b() & a() = " +
(b() & a()));
trace("----------");
trace("a() | b()
= " + (a() | b()));
trace("----------");
trace("b() |
a() = " + (b() | a()));
Результат выполнения сего кода
таков:
function a called
function b called
a()
&& b() = false
----------
function b
called
b() && a() = false
----------
function
a called
a() || b() = true
----------
function b
called
function a called
b() || a() =
true
========================
function a
called
function b called
a() & b() =
0
----------
function b called
function a
called
b() & a() = 0
----------
function a
called
function b called
a() | b() =
1
----------
function b called
function a
called
b() | a() = 1
Видим, что замена логических
операторов побитовыми действительно помогает добиться
вычисления обеих частей каждого выражения.
Теперь
посмотрим, что произойдет, если наши функции будут вести себя
не так хорошо и станут возвращать целочисленные значения
вместо булевских. Заменив в предыдущем примере функции а и b
на следующие две
a = function(){
trace("function a
called");
return 1;
};
b =
function(){
trace("function b called");
return
-2;
};
мы получим:
function a called
function
b called
a() && b() = -2
----------
function
b called
function a called
b() && a() =
1
----------
function a called
a() || b() =
1
----------
function b called
b() || a() =
-2
========================
function a
called
function b called
a() & b() =
0
----------
function b called
function a
called
b() & a() = 0
----------
function a
called
function b called
a() | b() =
-1
----------
function b called
function a
called
b() | a() = -1
Обратите внимание, что в серии
тестов с операторами & мы получили неправильные ответы -
это из-за того, что мы пренебрегли приведением к Boolean. А
вот операнды операторов | очень редко нуждаются в таком
приведении: если один или оба - ненулевые, то и результат
будет ненулевым. Неожиданности будут подстерегать нас лишь
когда мы столкнемся с объектом, который не приводится к числу
"естественным" образом и не является при этом строкой. То есть
с объектом, имеющим тип Object, Function или другой подобный.
При неявном приведении к типу Number для осуществления
побитовых операций, из такого объекта получится Number.NaN. А
это значение как для булевских, так и для побитовых операций
равносильно 0. При непосредственном же приведении к булевскому
типу - в случае использования оператора ||, а не | - объекты
типа Object, Function, Array и т.д. преобразуются в
true.
Еще одно интересное наблюдение мы можем сделать, если
посмотрим на результаты работы логических операторов. В
отличие от прошлого примера, когда мы получали true или false
(потому что булевские значения возвращали функции a и b), в
этот раз мы имеем целые числа. То есть "на выходе" логического
оператора никакого преобразования к Boolean не происходит.
Таким образом, можно представить себе работу оператора
&& в выражении a() && b() как (temp = a()) ?
b() : temp. В самом деле, в случае, когда a() дает истину,
результат будет истинным, лишь если истинно b() - его и
возвращаем. Если же а() есть ложь, то результат заведомо
ложен, так что в качестве него можно а() и вернуть.
Аналогичным образом работу оператора || в выражении a() || b()
можно эмулировать вот так: (temp = a()) ? temp : b() . То есть
если а() - истина, то и результат всего выражения - истина,
так что смело возвращаем а(). А если же а() - ложь, тогда
возвращаем b(), поскольку в этом случае истину мы получим
только если истинно b(). Еще раз отметим, что хотя сейчас мы
рассуждения проводили в булевских терминах, поведение реальных
операторов и эмуляции совпадает во всех случаях. Что мы сейчас
продемонстрируем на следующем примере.
a =
function(){
trace("function a called");
return "Some
string";
};
b = function(){
trace("function b
called");
return -2;
};
trace("((temp = a()) ? b() :
temp) =
" + ((temp = a()) ? b() :
temp));
trace("----------");
trace("((temp = b()) ? a()
: temp) =
" + ((temp = b()) ? a() :
temp));
trace("----------");
trace("((temp = a()) ? temp
: b()) =
" + ((temp = a()) ? temp :
b()));
trace("----------");
trace("( (temp = b()) ? temp
: a() ) = " + ((temp = b()) ?
temp :
a()));
trace("\n========================\n");
trace("a()
&& b() = " + (a() &&
b()));
trace("----------");
trace("b() && a() =
" + (b() &&
a()));
trace("----------");
trace("a() || b() = " + (a()
|| b()));
trace("----------");
trace("b() || a() = " +
(b() || a()));
И получаем:
function a called
(
(temp = a()) ? b() : temp ) = Some
string
----------
function b called
function a
called
( (temp = b()) ? a() : temp ) = Some
string
----------
function a called
function b
called
( (temp = a()) ? temp : b() ) =
-2
----------
function b called
( (temp = b()) ? temp
: a() ) = -2
========================
function a
called
a() && b() = Some
string
----------
function b called
function a
called
b() && a() = Some
string
----------
function a called
function b
called
a() || b() = -2
----------
function b
called
b() || a() = -2
То есть наше представление
операторов && и || с помощью оператора ?: оказалось
правильным. А это значит, что в некоторых случаях удобнее
применять операторы && и || вместо ?: - сокращается
запись и не нужно сохранять результат вычисления выражения,
используемого дважды, во временную переменную. Например, мы
запрашиваем нужный нам набор параметров функцией
getParamArray(), но если этот массив не задан (undefined или
null), используем набор параметров по умолчанию. Стандартный
код выглядит так:
getParamArray() ? getParamArray() :
getDefaultParamArray()
что длинно и заставляет вызывать
getParamArray() дважды (или надо будет использовать временную
переменную). Альтернативный вариант такой: getParamArray() ||
getDefaultParamArray(). Согласитесь, что писать так гораздо
удобнее. Только, если вы действительно соберетесь использовать
этот прием, не забудьте снабдить такой код комментариями.
Все-таки подобная запись совершенно не является
общепринятой.
Скажем еще несколько слов по поводу побитовых
операторов. Они фактически работают с двоичным представлением
числа, а мы уже знаем, что работа с недесятичными системами
счисления проводится в 32 битах. Если вы захотите проделать
побитовую операцию с числом, которое больше или равно 232, то
будут взяты только 32 младших бита этого числа.
Последовательность действий Флэш МХ в этом случае можно
описать примерно так. Сначала операнд представляется в форме
действительного числа с фиксированной точкой. Затем
отбрасывается дробная часть. Затем вычисляется остаток от
деления этого числа на 232. С этим остатком и производятся все
побитовые операции. Вот пример, иллюстрирующий все
это:
trace((1e12 + 0.6) + " = 1e12 + 0.6");
trace((1e12
+ 0.6).toString(2) + " = (1e12 +
0.6).toString(2)");
trace(1e12.toString(2) + " =
1e12.toString(2)" + "\n");
trace(1e12.toString(2) + " в
десятичном виде = " +
parseInt(1e12.toString(2),
2));
trace("\n" + ((1e12 + 0.6) | 7) + " = (1e12 + 0.6) |
7");
trace(((1e12 % Math.pow(2,32)) | 7) + "
= (1e12 %
Math.pow(2,32)) | 7");
trace("\nВ двоичном виде:
");
trace(((1e12 + 0.6) | 7).toString(2) + "
= ((1e12 +
0.6) | 7).toString(2)");
trace(((1e12 % Math.pow(2,32)) |
7).toString(2) + "
= ((1e12 % Math.pow(2,32)) |
7).toString(2)");
что дает в
результате
1000000000000.6 = 1e12 +
0.6
-101011010110101111000000000000 = (1e12 +
0.6).toString(2)
-101011010110101111000000000000 =
1e12.toString(2)
-101011010110101111000000000000 в десятичном виде = -727379968
-727379961 = (1e12 + 0.6) | 7
-727379961 = (1e12 %
Math.pow(2,32)) | 7
В двоичном виде:
-101011010110101110111111111001 =
((1e12 + 0.6) |
7).toString(2)
-101011010110101110111111111001 = ((1e12 %
Math.pow(2,32)) |
7).toString(2)
Мы нарочно
напечатали результаты вычислений слева, чтобы числа оказались
одно под другим и равенство полученных разным способом чисел
было очевидно. Легко заметить, что результаты побитовой
операции существенно меньше, чем 1012, так что отбрасывание
старших битов (равносильное делению по модулю на 232)
действительно свершилось. А описанный нами алгоритм дал тот же
результат, что и прямое выполнение операции "побитовое или".
(Для наглядности мы представили результаты как в десятичном,
так и в двоичном виде.)
Кроме того, из Флэш 4 перешел во Флэш 5 и Флэш МХ набор логических операторов, полностью аналогичных операторам ! (отрицание), && (логическое "И") и || (логическое "ИЛИ"). Вот они:
Также имеется устаревшая форма оператора сравнения != (возвращающего true, если сравниваемые выражения не равны). Этот оператор имеет такой вид: <> (его можно встретить, скажем, в языке Бейсик). Итак, вносим в нашу таблицу устаревших операторов
Наконец, последним в списке устаревших операторов является оператор, предназначенный для склеивания строк:
Этот оператор преобразует свои операнды в строки, а затем
работает точно так же, как со строками работает оператор +.
Еще
раз повторим, что все эти операторы считаются устаревшими и
использовать их не рекомендуется.
Таблица 2.3. Приоритеты операторов | |||
Приоритет |
Операторы |
Тип операторов |
Комментарии |
1 |
() |
Группировка |
|
2 |
[] . |
Доступ к полю или элементу |
|
3 |
- ~ ! ++ - typeof void |
Унарные |
Вычисляются справа налево |
4 |
* / % |
Мультипликативные |
|
5 |
+ - |
Аддитивные |
|
6 |
<< >> >>> |
Бинарный сдвиг |
|
7 |
< > <= >= instanceof |
Отношение |
|
8 |
== != === !== |
Проверка равенства |
|
9 |
& |
Поразрядное И (AND) |
|
10 |
^ |
Поразрядное исключающее ИЛИ (XOR) |
|
11 |
| |
Поразрядное ИЛИ (OR) |
|
12 |
&& |
Логическое И (AND) |
|
13 |
|| |
Логическое ИЛИ (OR) |
|
14 |
?: |
Условный оператор |
Вычисляется справа налево |
15 |
= *= /= %= += -= <<= >>= >>>= &= ^= |= |
Присваивание (простое и составное) |
Вычисляются справа налево |
16 |
, |
Последовательное вычисление |
Специфичные управляющие конструкцииНа самом деле, специфичной для Флэш является всего одна управляющая конструкция: это оператор for...in. Служит он для перебора всех полей некоторого объекта. Далее (в лекции 4, посвященной контейнерам Флэш МХ) мы увидим, что любой объект во Флэше фактически является хэш-таблицей: по ключу (имени переменной) выдается записанная информация. Перебор всех ключей, по которым записано хоть что-нибудь, - это и есть задача оператора for...in. Под "чем-нибудь" - понимаются обычные переменные, функции, номера элементов массива, если мы применили оператор for...in к массиву, и даже скрытые встроенные поля и методы - если только мы сможем "раскрыть" их. Последнее нужно редко и делается только при помощи недокументированных функций (но об этом мы все же будем говорить в лекции, посвященной наследованию). А вот как делается перебор полей объектов и массивов: a_array = [4,6,8, 1212]; b_obj = {x: 15, y: "some text"} for (var name in a_array){ |
|
Как и в С++, во Флэш МХ есть директива #include. И хотя возможности ее здесь не столь широки (в первую очередь из-за отсутствия препроцессора), польза ее несомненна.
В первую очередь давайте создадим файл по имени inc1.as (расширение as есть сокращение от слов Action Script). Затем создадим новый флэш-документ (*.fla) и сохраним его в ту же директорию, где лежит inc1.as. В inc1.as напишите, например,
trace("inc1.as included");
Во флэш-документе в первом кадр поместите код
#include "inc1.as"
Обратите внимание, что точку с запятой ставить после этой
директивы нельзя. Вообще ничего лишнего в строке после директивы
писать нельзя (а вот до знака #, как это ни странно, иногда можно,
но лучше не пишите там ничего; пользы от этого все равно никакой не
будет). Кавычки должны быть обязательно двойными.
После запуска
этого кода вы увидите, что в консоль выдана строчка inc1.as included
- включение файла сработало!
При работе с #include допускаются
включения файлов по цепочке. Также можно указывать путь к файлу,
считая от директории, куда сохранен *.fla-файл. Допускаются также
полные пути с указанием диска. В пути можно использовать прямые и
обратные слэши, причем в любых количествах (то есть обратные слэши
могут быть одинарные, двойные или даже четверные - все равно это
работает. Даже прямые слэши можно удваивать и утраивать, если есть
на то желание). Не забудьте, что если вы включаете файлы по цепочке
и указываете не абсолютные пути (с именем диска), а относительные, -
в любом из файлов пути будут отсчитываться от директории, в которую
сохранен исходный флэш-документ.
Наконец, упомянем, что
включаемые файлы можно сохранять не только в кодировке ANSI, но и в
UTF-8 (это может быть полезно, например, если вы используете
строковые константы на различных языках). Однако если вы сохраняете
включаемый файл в UTF-8, его первыми символами обязательно должны
быть //!-- UTF8 (никаких пробелов перед знаком комментария и строго
один пробел перед надписью UTF8). В противном случае компилятор не
сможет подключить такой файл и выдаст ошибку.
Включение кода из указанного файла происходит в момент
компиляции. Более того, никаких средств условной компиляции
(наподобие директив препроцессора в С) во Флэше нет. Таким образом,
повлиять на то, будет ли включен определенный код в ваш флэш-ролик,
или же не будет, нет никакой возможности. Будет! А если нужный файл
не будет найден по указанному вами пути, при компиляции будет выдана
ошибка. Так что если вам очень нужно подменять включаемый код, вам
придется для этого пользоваться средствами операционной системы
(сетевыми дисками, командой subst, soft- и
hard-линками). О том, как это удобнее всего
устроить, читайте в лекции про средства коллективной работы.
Зато
вам не нужно заботиться о ваших включаемых файлах при запуске
флэш-ролика. В отличие от файлов с переменными и настройками,
которые вы можете подгружать явным образом при помощи объекта
LoadVars (см. лекцию 14), включаемые файлы после компиляции больше
не нужны.
Управлять тем, включается ли заданный код в ваш ролик в зависимости от определенных условий вы (средствами Флэш) не можете, однако вы можете воздействовать на то, выполняется ли этот код. То есть вы вполне можете вставить директиву #include внутри блока, предваренного инструкцией if. Или даже - внутри определения какой-либо функции. Дальнейшая ваша задача - добиться того, чтобы код и в этом обрамлении работал так, как надо. Здесь есть ряд тонких моментов, связанных, например, с определением функций внутри блоков или внутри других функций. Эти моменты будут подробно разобраны в лекции 5, посвященной функциям Флэш МХ. В частности, там разобран вопрос о том, как организовать стражи включения для файлов, содержащих определения функций. Эта задача содержит в себе подвох, поскольку для ее решения подходит лишь один (причем менее употребительный) из двух существующих во Флэш МХ методов определения функций. Поэтому прочтите лекцию 5, прежде чем начнете организовывать стражи включения.
Отладка во Flash MXВы уже, наверное, успели понять, что роль отладки во Flash MX сложно переоценить. Говоря нормальным языком - отлаживать вам придется, и немало. Ведь во Flash MX компилятор почти не помогает программисту. Единственное, что он поможет выловить на уровне компиляции - это простейшие синтаксические ошибки типа несоответствия фигурных скобок или отсутствия точки с запятой. Все остальное, к сожалению, остается за вами. В этой лекции мы рассмотрим использование встроенного отладчика, а также коротко пройдемся по другим методам отладки, детально рассматриваемым в лекции "Работа флэш-программ в Internet". Использование встроенного отладчикаВо Flash MX имеется достаточно мощный встроенный отладчик, предоставляющий различные необходимые инструменты отладки: точки останова, пошаговое выполнение программы, просмотр значений переменных и вычисление выражений (watches). Для того чтобы запустить флэш-ролик под отладчиком, выберите Control / Debug Movie (Ctrl+Shift+Enter). Кстати, обратите внимание, что при этом создается файл с отладочной информацией (.swd-файл), без которого большинство отладочных инструментов недоступно (этот файл должен лежать в том же каталоге, что и отлаживаемый .swf-файл). Структура отладчикаПо нажатию Ctrl+Shift+Enter перед вами открывается отладчик, окно которого состоит из четырех частей (дочерних окон).
Панель инструментов отладчикаПанель инструментов состоит из 7 кнопок. Рассмотрим их слева направо. Continue - начать/продолжить выполнение ролика; Stop Debugging - прекратить отладку (дальнейшее выполнение ролика продолжается без управления отладчика); Toggle Breakpoint - поставить/убрать точку останова ("line" breakpoint) в данном месте; Remove All Breakpoints - убрать все точки останова; Step Over - сделать шаг вперед, не заходя в функцию/метод; Step In - сделать шаг вперед с заходом в функцию/метод; Step Out - выйти из функции/метода и остановиться на следующем операторе. Итак, вы запустили отладчик, теперь нужно один раз нажать на кнопку Continue для запуска ролика (поставив перед этим все необходимые точки останова) - и можно отлаживать. Базовые принципы отладки, такие, как пошаговое выполнение и точки останова, мы детально рассматривать не будем, ибо они совершенно аналогично действуют в любой другой среде разработки, остановимся подробнее на той части отладчика, которая расположена слева. Просмотр значений свойств, переменных клипов и стека (locals) Если выбрать закладку Properties или Variables в левой средней части окна отладчика, то под ней можно увидеть список свойств (или, соответственно, переменных) клипа, выбранного в левой верхней части окна отладчика, а также их значений. Выбрав закладку Locals, вы увидите список локальных переменных с указанием их значений (стек) контекста исполнения, указанного в нижнем левом окне отладчика (Call Stack). Полезный совет: не забывайте открывать (с помощью значка плюса) такие объекты, как this, чтобы увидеть значения их полей. Вычисление выражений и корректировка их значений (watch, evaluate/modify)Если нужно отредактировать значение свойства, переменной клипа или локальной переменной во время отладки, это можно сделать, щелкнув дважды на их текущем значении (в левом среднем окне отладчика) и вписав новое значение. Во Flash MX есть специальная закладка - Watch, которая позволяет наблюдать за состоянием важных для вас переменных клипов и локальных переменных. Для добавления переменных клипов и локальных переменных в закладку нажмите на имя переменной (отображаемой в закладке Variables или Locals) правой кнопкой мыши и выберите в контекстном меню пункт Watch. Добавить переменную в окно Watch можно также, если воспользоваться пунктом Add контекстного меню этого окна. При этом в окне создается пустая строчка, в левой части которой нужно вписать имя переменной, тогда в правой части появится ее значение. Обратите внимание, что имена переменных должны быть записаны единственно правильным способом, с использованием абсолютного пути. Например, для переменной a, созданной в _root, правильным именем будет "_level0.a", но не "а" и не "_root.a". Очевидно, данное ограничение уместно только в окне Watch. Значения переменных, отображаемые в закладке Watch, также можно изменять (просто вписывая новое значение). Точки останова в отладчике и в программеСледует отметить один интересный факт, касающийся точек останова: если их расставлять уже в отладчике, то они будут действительны только на протяжении данной сессии отладки. Поэтому удобнее ставить часто используемые точки останова еще при написании кода, до запуска отладчика, тогда они будут действовать на все сессии отладки. Выбор нужного контекста выполненияКак мы уже ранее заметили, в правой части отладчика отображается отлаживаемый код. Что делать, если нужно, например, поставить точку останова в другом контексте исполнения (например, в другом кадре или другом классе)? Для этого предназначен выпадающий список выбора контекста исполнения, в котором перечислены все элементы, содержащие код (кадры, код, ассоциированный с клипами и т. д.). Правда, этим линейным списком очень неудобно пользоваться, если ролик большой и элементов с кодом много. В таких случаях бывает удобнее заранее расставить все необходимые точки останова перед отладкой. Нужны ли другие методы отладки?Это вопрос, над которым стоит задуматься. Если ваш проект небольшой, вам должно хватить возможностей встроенного отладчика. И даже если какие-то ошибки проявляются только в браузере, вы сможете воспользоваться средствами удаленной отладки. Правда, для этого вам надо будет установить отладочную версию плеера, но дело того стоит. Однако в большом проекте могут окупиться и затраты по включению дополнительных отладочных возможностей прямо в библиотеку, которую вы для этого проекта делаете. Вы вполне можете положить поверх вашего ролика текстовую панель, которая все время скрыта, но может быть показана по нажатию какой-нибудь клавиши (например, F12). Получится своего рода аналог отладочной консоли. В эту консоль ваши методы (если установлена в true заведенная вами переменная "отладочный режим") могут выводить информацию о своей работе (не только об ошибках). Конечно, в вашей воле поддерживать несколько отладочных режимов с разной степенью детализации информации, выдаваемой в консоль. Опыт показывает, что в каждом кадре в консоль вполне может выдаваться не один десяток строчек, и это практически не влияет на производительность (если не хранить слишком большое количество строчек). И, наконец, вы, конечно же, захотите протестировать ваш проект "в реальных условиях", то есть, на реальной для Интернета пропускной способности канала. Для этого можно использовать либо встроенный во Flash MX Bandwidth profiler, либо специальный веб-сервер. Обо всех упомянутых только что методах отладки - удаленной отладке, отладочной консоли и о тестировании на канале малой пропускной способности - мы подробно расскажем в лекции "Работа флэш-программ в Internet". |