То, как ActionScript реализует
Самый простой способ определить
function func_name(arg1, arg2){
// здесь определяем тело функции
return ret_value; // возвращаем значение
}
То есть почти как в С - только вместо типа возвращаемого значения мы пишем ключевое слово function. Кроме того, не указываются типы аргументов. Чтобы вернуть из return. Если вслед за return сразу идет точка с запятой - получим аналог void из С, то есть никакое значение не возвращается. Наконец, можно вовсе не писать return. Словом, все как обычно.
А вот второй способ определить
func_name = function (arg1, arg2){
// здесь определяем тело функции
return ret_value; // возвращаем значение
}
То, что здесь написано, полностью эквивалентно первому func_name - это что-то вроде func_name является полем того клипа, в Actions которого мы определяем нашу function играет роль оператора генерации нужной нам
И действительно, в отличие от первого способа, второй можно применять внутри многих других конструкций языка, что предоставляет массу дополнительных возможностей использования. В частности, вы можете реализовать что-то вроде "условной трансляции" и " #include работает в процессе компиляции *.swf-файла, то на любой код, стоящий вокруг нее, она внимания не обращает. "Зацикливание" включаемых файлов приводит к генерации синтаксической ошибки и избавиться от этого зацикливания мы сможем только путем перекомпоновки включаемых файлов, никакие "условные включения" здесь не помогут.
Однако есть другие задачи, для которых в С++ часто используются директивы препроцессора. Первая из них - чтобы все
inc1_as = true;
_global.func = function(arg){
//code
}
При подключении обрамляем оператор #include условным оператором. При этом подключение произойдет в любой случае, но выполнен этот код будет только если выполнено условие проверки.
if (!inc1_as){
#include "inc1.as"
}
Вторая задача: иногда надо в зависимости от некоторого условия использовать тот или иной набор
В файле inc1.as пишем:
_global.func = function(arg){
//code1
}
В файле inc2.as пишем, в свою очередь:
_global.func = function(arg){
//code2 - отличие здесь
}
И внутри нашего флэш-ролика поступаем так:
if (!advancedMode){
#include "inc1.as"
}
else{
#include "inc2.as"
}
В обоих случаях только второй способ _global. То есть нам не придется заново подключать файл с
Однако, говоря об использовании оператора генерации . Различным вариантам такого использования посвящена вся эта лекция. Чтобы вы сразу увидели, что мы понимаем под "необычным" (для С++ и Java) использованием, взгляните на такой код.
iter2 = function(f){
return function(x){ return f(f(x)) }
}
trace(iter2(function(y){return y*y*y;})(2));
Этот код выводит число 512. Здесь есть два заслуживающих внимания момента: во-первых, мы написали
function progressionSum(low, hi, step){
for (i=low; i<=hi; i+=step) sum+=i;
// Здесь нарочно не пишем var как будто бы мы не знаем, что это
return sum;
}
// Используем нашу функцию
trace("sum = " + progressionSum(4, 10, 3));
//Вроде бы делаем то же самое
trace("sum = " + progressionSum(4, 10, 3));
Этот код выводит следующее:
sum = 21 sum = 42
То есть программист, решивший положиться на то, что Флэш сам заводит переменную, если в нее хотят что-то записать (и при этом делает ее равной нулю), должен хорошо представлять себе, где именно Флэш эту переменную заведет. В данном случае было заведено поле в том клипе, в котором определена progressionSum (и, конечно, записанное в нем значение сохранилось до следующего вызова). Так что var. В результате заводится обычная флэш-переменная, только теперь объект, в котором она лежит - это
Вот исправленный пример progressionSum:
function progressionSum(low, hi, step){
var sum;
for (i=low; i<=hi; i+=step) sum+=i;
return sum;
}
trace("sum = " + progressionSum(4, 10, 3));
trace("sum = " + progressionSum(4, 10, 3));
На сей раз мы получаем
sum = 21 sum = 21
как того и следовало ожидать. (В этом варианте
Однако не следует думать, что с var будет попросту проигнорировано). Точнее, все будет именно так, если код в кадре выполняется обычным образом - в тот момент, когда до этого кадра дошла очередь в линейке времени. Если же код из кадра был вызван при помощи команды Call (не путайте с методом Function.call, о котором далее), то переменная, объявленная с помощью var все-таки будет Call. Мы ведь уже говорили, что, в отличие от метода Function.call, команда Call - это типичный атавизм.
var везде, где только возможно (чтобы не засорять пространство имен объемлющего объекта). Например, в progressionSum из предыдущего примера все же была допущена ошибка, в чем мы убедимся, запустив такой код:
for (i=0; i<4; i++)
trace("i = " + i + "; sum = " + progressionSum(i, 10, 3));
В результате мы получим
i = 0; sum = 18
- всего одну строчку, хотя ожидали получить четыре. Ошибка, разумеется, в том, что в цикле for внутри
Так что следовало написать:
function progressionSum(low, hi, step){
var sum;
for (var i=low; i<=hi; i+=step) sum+=i;
return sum;
}
for (i=0; i<4; i++)
trace("i = " + i + "; sum = " + progressionSum(i, 10, 3));
и тогда мы получим
i = 0; sum = 18 i = 1; sum = 22 i = 2; sum = 15 i = 3; sum = 18
Хотелось бы, конечно, сделать и внешнюю переменную i
Итак, область видимости
i = 3;
function a(){
trace("function a: variable i = " + i);
var i=5;
trace("function a: variable i = " + i);
function b(){
trace("function b: variable i = " + i);
var i=7;
trace("function b: variable i = " + i);
}
trace("function a: variable i = " + i);
var i=6;
b();
trace("function a: variable i = " + i);
}
a();
Этот код выводит следующее:
function a: variable i = 3 function a: variable i = 5 function a: variable i = 5 function b: variable i = 6 function b: variable i = 7 function a: variable i = 6
Обратите внимание, что до тех пор, пока в b() не была объявлена собственная переменная i, использовалась переменная из a(), то есть из того b() была определена. (Но значение этой переменной бралось, разумеется, то, которое было на момент вызова, а не на момент b() снаружи, то она, так же, как и a(), использовала бы "внешнюю" переменную i, несмотря на то, что вызывалась бы внутри a(). Действительно, код
i = 3;
function b(){
trace("function b: variable i = " + i);
var i=7;
trace("function b: variable i = " + i);
}
function a(){
trace("function a: variable i = " + i);
var i=5;
trace("function a: variable i = " + i);
// Раньше здесь было определение b()
trace("function a: variable i = " + i);
var i=6;
b();
trace("function a: variable i = " + i);
}
a();
выводит
function a: variable i = 3 function a: variable i = 5 function a: variable i = 5
function b: variable i = 3 function b: variable i = 7 function a: variable i = 6
(Полужирным курсивом выделена строчка, в которой видно отличие от предыдущего примера.) По этой же причине попытка заменить большое количество похожих вызовов trace вызовом специальной
В самом деле, код
function traceI(funcName){
trace("function " + funcName + ": variable i = " + i);
}
i = 3;
function a(){
traceI("a");
var i=5;
traceI("a");
function b(){
traceI("b");
var i=7;
traceI("b");
}
traceI("a");
var i=6;
b();
traceI("a");
}
a();
и вовсе выводит
function a: variable i = 3 function a: variable i = 3 function a: variable i = 3 function b: variable i = 3 function b: variable i = 3 function a: variable i = 3
- очевидно, совсем не то, что мы хотели видеть. Но поделать ничего нельзя: i в фукнции a() и тем более b(), для traceI(funcName), определенной "снаружи", недоступны. Она видит только поле i того объекта, в котором помещен наш код.
Наконец, отметим, что и сама b() является своего рода "
Как обычно, начинаем с примера:
a();
function a(){
trace("function a called");
function b(){
trace("function b: first incarnation");
}
b();
function c(){
trace("function c called")
b();
function b(){
trace("function b: second incarnation");
}
b();
}
c();
b();
trace("--------------");
}
a();
b(); //Ничего не делает
Вас может слегка удивить, что первый раз а() вызвана до своего
function a called function b: first incarnation function c called function b: second incarnation function b: second incarnation function b: first incarnation -------------- function a called function b: first incarnation function c called function b: second incarnation function b: second incarnation function b: first incarnation --------------
Мы видим, что и первый вызов а() замечательно сработал! Также мы видим, что внутри с() всегда вызывается вторая инкарнация b(), а снаружи - первая. (Снаружи а() попытка вызвать b() вовсе не дает никакого результата). Таким образом, то, какая версия b() будет вызываться внутри другой а() и с() ) решается на этапе компиляции. Мы можем это представлять себе так: компилятор находит в коде все вызовы
a();
function a(){
trace("function a called");
function b(){
trace("function b: first incarnation");
}
b();
function c(){
trace("function c called")
b();
b = function(){
trace("function b: second incarnation");
}
b();
}
c();
b();
trace("--------------");
}
a();
b(); //Ничего не делает по-прежнему
дает на выходе
function a called function b: first incarnation function c called function b: first incarnation function b: second incarnation function b: second incarnation -------------- function a called function b: first incarnation function c called function b: first incarnation function b: second incarnation function b: second incarnation --------------
Вот так раз! Внутри с() мы добились того, что хотели - до определения второй инкарнации b() была вызвана первая. Но при этом эта первая инкарнация, определенная в вышележащей а(), была испорчена! (Заметим также, что снаружи а() вызвать b() по-прежнему не удается. Этот факт - обратная сторона того, что новая реализация записалась поверх старой и ее испортила. Старая-то была локальной! Если бы старой реализации не было вовсе - вот тогда запись b = function() означала бы создание новой переменной текущего клипа, содержащей в себе
И только если мы сделаем вторую инкарнацию b() по-настоящему локальной с помощью ключевого слова var (см. код, здесь это слово выделено жирным шрифтом):
a();
function a(){
trace("function a called");
function b(){
trace("function b: first incarnation");
}
b();
function c(){
trace("function c called")
b();
var b = function(){
trace("function b: second incarnation");
}
b();
}
c();
b();
trace("--------------");
}
a();
b(); //Ничего не делает по-прежнему
то наконец-то получим
function a called function b: first incarnation function c called function b: first incarnation function b: second incarnation function b: first incarnation -------------- function a called function b: first incarnation function c called function b: first incarnation function b: second incarnation function b: first incarnation
Итак, нам удалось сделать полностью
Вы наверняка помните, что среди небольшого количества типов данных, поддерживаемых во Флэш МХ, имеется тип . А это значит, что мы можем создавать объекты этого типа и переменные, ссылающиеся на эти объекты. Наконец, каждый тип данных во Флэш МХ предназначен для того, чтобы с ним особым образом работали некоторые операторы - для функционального типа это круглые скобки, оператор вызова
Итак, в некоторой переменной может лежать
Подтвердить такую точку зрения может использование нескольких
var a = function(){
trace("function a called");
}
var b = a;
a();
b();
(Переменные, в которых хранятся var всегда, когда это не противоречит вашим намерениям.)
Этот код, как и следовало ожидать, выводит:
function a called function a called
Здесь мы определяли
var d = c;
d();
function c(){
trace("function c called");
}
Этот код, хотя и не является интуитивно понятным, все же выводит именно
function с called
Мы еще раз убеждаемся в том, что
Про эту красивую возможность мы не раз уже говорили. Пора, наконец-то, привести простой и наглядный пример.
var d = ["a", "b", "c", "d"];
iterate(d, doubleString);
trace(d.join(", "));
iterate(d, capitalize);
trace(d.join(", "));
function iterate(arr, func){
for (var i=0; i<arr.length; i++) arr[i] = func(arr[i]);
}
function doubleString(str){
return str+str;
}
function capitalize(str){
return str.toUpperCase();
}
Этот код выводит
aa, bb, cc, dd AA, BB, CC, DD
В этом примере, кроме передачи _global (откуда они, напомним, доступны для любого _level, то есть всем загруженным *.swf-роликам. Причем, если нет конфликта имен, доступны без явного указания _global ). Поэтому мы, как правило, будем использовать именно
Мы уже видели, что любая ссылка может указывать на return. Получив эту ссылку, мы можем немедленно вызвать ту . Вот пример кода, который позволит ответить на наши вопросы.
t1 = "Снаружи функции";
function test(arg){
var t1 = "Внутри функции";
var func = function(){
trace("this.arg = " + this.arg);
trace("this.t1 = " + this.t1);
trace("t1 = " + t1);
}
trace("---- Вызываем func() ------");
func();
trace("------------------");
return func;
}
trace("======= Вызываем test()() ==============");
test()();
trace("");
trace("======= Вызываем а = test(); a() =======");
a = test("argString");
a();
На выходе получаем:
======= Вызываем test()() ============== ---- Вызываем func() ------ this.arg = this.t1 = Внутри функции t1 = Внутри функции ------------------ this.arg = this.t1 = t1 = Внутри функции ======= Вызываем а = test(); a() ======= ---- Вызываем func() ------ this.arg = argString this.t1 = Внутри функции t1 = Внутри функции ------------------ this.arg = this.t1 = Снаружи функции t1 = Внутри функции
Выводы отсюда можно сделать следующие.
this внутри arguments , о котором речь пойдет дальше: arguments, - это массив и аргументы лежат в нем по номерам). На этот-то объект, описывающий this в сгенерированной test()(); ), то test, поскольку и в том и в другом мы заводили переменную t1 ; а результат вызова test()(); говорит об отсутствии этой переменной в обсуждаемом объекте.Прием конструирования при передаче особенно удобен при сортировке массивов (мы его даже уже применяли, когда рассказывали о массивах):
var arr = ["aaaaaaaa", "bbb", "cc", "dddd", "e", "gg"];
arr.sort(
function(str1, str2){
return (str1.length > str2.length) ?
1 : ((str1.length == str2.length) ? 0 : -1);
}
);
trace(arr.join(", "));
В результате получаем:
e, cc, gg, bbb, dddd, aaaaaaaa
то есть мы отсортировали массив по возрастанию длины строк. В этом примере привлекает то, что вся информация сосредоточена в одном месте: прямо в вызове метода sort мы и указываем, как именно надо сортировать.
Сейчас мы покажем вам пример, из которого следует, насколько нужно быть осторожным при копировании а метод, вычисляющий сумму прогрессии по рекурсивному алгоритму, а потом пытаемся скопировать этот метод в другой объект.
a = {};
a.recProgressionSum = function(n){
if (n < 1) return n;
return n + this.recProgressionSum(n - 1);
}
trace("from a: " + a.recProgressionSum(5));
trace("from a: " + a.recProgressionSum(5.5));
b = {};
b.recFunction = a.recProgressionSum;
trace("from b: " + b.recFunction(5));
trace("from b: " + b.recFunction(5.5));
В результате выполнения этого кода мы получим следующее:
from a: 15 from a: 18 from b: 5 from b: 5.5
Причина такого b нет recProgressionSum, к которой имеется рекурсивное обращение. Идея явно указать объект, в котором эта а (или просто не запишем туда что-нибудь другое):
a = {};
a.recProgressionSum = function(n){
if (n < 1) return n;
return n + a.recProgressionSum(n - 1);
}
trace("from a: " + a.recProgressionSum(5));
trace("from a: " + a.recProgressionSum(5.5));
b = {};
b.recFunction = a.recProgressionSum;
trace("from b: " + b.recFunction(5));
trace("from b: " + b.recFunction(5.5));
a = {dddd: 1};
trace("from b: " + b.recFunction(5));
trace("from b: " + b.recFunction(5.5));
Этот код выводит
from a: 15 from a: 18 from b: 15 from b: 18 from b: 5 from b: 5.5
О том, как справиться с подобной бедой, мы узнаем в следующем параграфе.
Программисты, пишущие на С (а уж тем более - на ассемблере) хорошо знакомы с так называемым стеком вызовов. Под этот стек отводится специальная область памяти, в которой указываются адреса вызывающих и вызываемых , который определен внутри каждой
- это массив, содержащий в себе все аргументы имеет поле length, в котором записана его длина. Кроме того, имеет еще два поля: и , через которые можно получить доступ к объектам вызывающей и вызываемой для того, чтобы сделать
Вот пример такой
_global.joinBy = function(glue){
var tmpString = ((arguments[1] != undefined) ? arguments[1] :
"");
for (var i = 2; i<arguments.length; i++){
tmpString += glue + arguments[i];
}
return tmpString;
}
trace("result = " + joinBy("___"));
trace("result = " + joinBy("___", "r"));
trace("result = " + joinBy("___", "E", "R", "T", "G"));
Выводит этот код следующие бессмертные строки:
result = result = r result = E___R___T___G
Впрочем, то же самое можно сделать и чуть покороче (хотя, возможно, и менее наглядно):
_global.joinBy1 = function(glue){
if (arguments.length == 2) return arguments[1];
var tmpString = arguments.join(glue);
return tmpString.substr(tmpString.indexOf(glue) +
glue.length);
}
trace("result = " + joinBy1("___"));
trace("result = " + joinBy1("___", "r"));
trace("result = " + joinBy1("___", "E", "R", "T", "G"));
Результат получается тот же самый.
Обратите внимание, что - это самый настоящий массив, у которого, в частности, работает метод join. Хотя по этому поводу и могут возникнуть небольшие сомнения, когда Флэш МХ автоматически подсказывает нам поля и методы, которые можно выбрать у объекта - в этом списке только length, и . Но сомнения напрасны, все методы, относящиеся к массивам, прекрасно работают и с массивом . Его можно даже отсортировать, хотя не так уж просто представить себе задачу, в которой это было бы необходимо.
Как мы уже писали, поля и позволяют получить доступ к объектам вызывающей и вызываемой действительно дает доступ к объекту вызываемой ), поле дает доступ к объекту вызывающей , зато можно было бы п
олностью раскрутить стек вызовов ( и т.д.). К сожалению, дела обстоят вовсе не так радужно, как утверждает документация. И указывает именно на объект вызывающей вызывающей не очень широка. В следующих лекциях мы приведем пример использовании в одной из специфических форм наследования. А пока что - вот пример использования для отладочных целей. Если мы заранее позаботимся о снабжении каждой name, то отладочная
_global.traceDebugInfo = function(){
trace("---------------");
trace("function: " + arguments.caller.name);
trace("a = " + a);
trace("---------------");
}
_global.doSomething = function(){
a++;
traceDebugInfo();
}
_global.doSomething.name = "doSomething";
_global.doAnything = function(){
a+=4;
traceDebugInfo();
}
_global.doAnything.name = "doAnything";
a = 3;
traceDebugInfo();
doAnything();
doSomething();
doAnything();
Приведенный здесь код выводит
--------------- function: a = 3 --------------- --------------- function: doAnything a = 7 --------------- --------------- function: doSomething a = 8 --------------- --------------- function: doAnything a = 12 ---------------
Обратите внимание, что мы можем вызвать нашу traceDebugInfo() в любом месте - не только в специально приспособленной name.
Область применения поля , в отличие от , достаточно широка. Сейчас мы продемонстрируем, как "опасная превращается в "безопасную". Для разнообразия будет использована другая рекурсивная n!! - это произведение всех натуральных чисел той же четности, что и n, которые меньше или равны n ).
a = {};
a.factEvenOdd = function(n){
return n > 2 ? n*arguments.callee(n - 2) : n;
}
trace("7!! = " + a.factEvenOdd(7));
b = {};
b.specialFact = a.factEvenOdd;
a.factEvenOdd = function(){return "function is obsolete"};
trace("8!! = " + a.factEvenOdd(8));
trace("8!! = " + b.specialFact(8));
Код, который здесь написан, выводит в консоль следующее:
7!! = 105 8!! = function is obsolete 8!! = 384
Итак, использование позволило нам копировать рекурсивную
Объект типа имеет два интересных метода: и . Оба они служат одной цели: вызвать или . Аргументы самой и либо в массиве (для ), либо подряд через запятую (для ). Внутри же самой будет указывать на первый аргумент или . Привести примеры полезного использования этих методов мы пока не можем; методы эти нужны в основном при работе с наследованием, так что нужно подождать до соответствующей лекции (а самые информативные примеры будут приведены в лекции о множественном наследовании). Тем не менее, упомянем об использовании объекта совместно с этими методами.
Итак, иногда бывает нужно сделать
a = {};
func = function(text){trace(text + this.field);}
a.field = 5;
a.f1 = func;
a.f1("a = ");
delete a.f1;
Конечно, это громоздкий способ сделать простую вещь. И во Флэш МХ появился другой, гораздо более простой и элегантный. Он основан как раз на использовании метода объекта Function (не путайте со встроенной устаревшей процедурой call ). С помощью этого метода предыдущий пример можно переписать так:
a = {};
func = function(){trace(this.field);}
a.field = 5;
func.call(a, "a = "); // Применяем функцию к объекту а
Как мы видим, на первом месте стоит объект, к которому применяем , в который, кроме объекта, в качестве метода которого вызывается
obj1 = {dataStr: "Строка из первого объекта"};
obj2 = {dataStr: "Строка из второго объекта"};
_global.applyToAnother = function(func, obj, args){
trace("Вызвана функция applyToAnother");
// Отрезаем конец массива аргументов - берем все,
// кроме первых двух
return func.apply(obj, arguments.slice(2));
}
_global.traceDataStr = function(begStr, endStr){
trace(begStr);
trace(this.dataStr);
trace(endStr);
}
traceDataStr("Начало: объект _global", "Конец: объект _global");
trace("--------------");
applyToAnother(traceDataStr, obj1, "Начало: первый объект",
"Конец: первый объект");
trace("--------------");
applyToAnother(traceDataStr, obj2, "Начало: второй объект",
"Конец: второй объект");
Этот код выводит:
Начало: объект _global undefined Конец: объект _global -------------- Вызвана функция applyToAnother Начало: первый объект Строка из первого объекта Конец: первый объект -------------- Вызвана функция applyToAnother Начало: второй объект Строка из второго объекта Конец: второй объект
Примеры более осмысленного использования методов и вы, как и было обещано, найдете в лекции о наследовании (параграф про вызов
Чем параметр
Рассмотрим задачу создания множества похожих обработчиков событий для семейства родственных контролов. Поскольку мы пока не имеем необходимых знаний о настоящих Флэш-контролах, то создадим сейчас несколько объектов, ведущих себя как контролы (по крайней мере, с точки зрения клиентского кода). Дополнительное неудобство заключается в том, что и с тем, как делаются на Флэше классы, мы пока также не сталкивались. Тем не менее, все эти трудности преодолимы. И в результате их преодоления может родиться, например, вот такой код:
controlsNumber = 5;
for(var i = 1; i<=controlsNumber; i++){
set(
"control" + i,
{
name: "control" + i,
curValue: 2*i+1,
oldValue: 3*i,
setValue: function(newValue){
trace(this.name + ".setValue(" + newValue + ") called");
this.oldValue = this.curValue;
this.curValue = newValue;
this.raiseEvent();
},
eventReactionFunction: null,
subscribe: function(func){
trace(this.name + ".subscribe() called");
this.eventReactionFunction = func;
},
raiseEvent: function(){
trace(this.name + ".raiseEvent() called");
this.eventReactionFunction(this.curValue);
}
}
);
}
// Здесь начинается "клиентский" код
_global.reactionFunction = function(){
trace("reaction");
}
for(var i = 1; i<=controlsNumber; i++){
eval("control" + i).subscribe(reactionFunction);
}
trace("------------------");
for(var i = 1; i<=controlsNumber; i++){
eval("control" + i).setValue(4*i + 1);
}
Как мы видим, сгенерирован ряд объектов с именами, начинающимися со слова control. Эти объекты имеют subscribe, сохраняющую raiseEvent ).
В результате запуска этого кода на выполнение мы получим:
control1.subscribe() called control2.subscribe() called control3.subscribe() called control4.subscribe() called control5.subscribe() called ------------------ control1.setValue(5) called control1.raiseEvent() called reaction control2.setValue(9) called control2.raiseEvent() called reaction control3.setValue(13) called control3.raiseEvent() called reaction control4.setValue(17) called control4.raiseEvent() called reaction control5.setValue(21) called control5.raiseEvent() called reaction
Пока что реакция на события от всех контролов одинакова. И мы никак не можем ее изменить, не подписывая в качестве reactionFunction дела не решат: raiseEvent() передает в raiseEvent() мы считаем не подлежащим изменениям. Попробуем выкручиваться с помощью параметров.
Сначала сформулируем задачу до конца. Предположим, что raiseEvent() передает нам только текущее значение curValue. Мы могли бы воспользоваться тем, что приватных полей во Флэш МХ нет, поэтому oldValue. Однако для этого надо иметь ссылку на нужный контрол (или по крайней мере его имя). Попробуем сделать это вот так:
for(var i = 1; i<=controlsNumber; i++){
var curControl = eval("control" + i);
reactionFunction.control = curControl;
curControl subscribe(reactionFunction);
// Тестируем:
curControl.setValue(4*i+3);
}
На первый взгляд все неплохо. Теперь нужно информацию из параметра извлечь в самой :
_global.reactionFunction = function(curValue){
var control = arguments.callee.control;
var result = curValue - control.oldValue;
trace("reaction: difference = " + result);
}
В результате полный текст "клиентского" кода будет выглядеть так:
_global.reactionFunction = function(curValue){
var control = arguments.callee.control;
var result = curValue - control.oldValue;
trace("reaction: difference = " + result);
}
for(var i = 1; i<=controlsNumber; i++){
var curControl = eval("control" + i);
reactionFunction.control = curControl;
curControl.subscribe(reactionFunction);
// Тестируем:
curControl.setValue(4*i+1);
}
trace("------------------");
for(var i = 1; i<=controlsNumber; i++){
eval("control" + i).setValue(4*i + 1);
}
Запускаем исправленный код и получаем:
control1.subscribe() called control1.setValue(5) called control1.raiseEvent() called reaction: difference = 2 control2.subscribe() called control2.setValue(9) called control2.raiseEvent() called reaction: difference = 4 control3.subscribe() called control3.setValue(13) called control3.raiseEvent() called reaction: difference = 6 control4.subscribe() called control4.setValue(17) called control4.raiseEvent() called reaction: difference = 8 control5.subscribe() called control5.setValue(21) called control5.raiseEvent() called reaction: difference = 10 ------------------ control1.setValue(5) called control1.raiseEvent() called reaction: difference = -6 control2.setValue(9) called control2.raiseEvent() called reaction: difference = -2 control3.setValue(13) called control3.raiseEvent() called reaction: difference = 2 control4.setValue(17) called control4.raiseEvent() called reaction: difference = 6 control5.setValue(21) called control5.raiseEvent() called reaction: difference = 0
Сначала вроде бы все хорошо: значения контролов были установлены в 2*i+1, а мы ставим их в 4*i+1, так что разница должна составить 2*i, что мы и видим (она равна для контролов с первого по пятый четным числам от 2 до 10). Однако в самом конце у нас стоит еще один цикл установки параметров контролов - мы не стали его убирать. И правильно сделали, ибо в нем-то и проявляются наши ошибки. Несмотря на то что мы опять устанавливаем каждому контролу значение 4*i+1, то есть разница со старыми значениями должна быть 0 для всех контролов, на деле мы получаем иное. Возможно, вы с самого начала увидели в чем состоит ошибка - тем не менее, мы хотели продемонстрировать наглядно тот факт, что объект control этого объекта каждый раз было
ссылкой на control5. Вывод: нам нужны пять разных объектов
В языке AсtionScript есть всего четыре способа создавать новые объекты (не считая примитивные). Это оператор new, с которым мы будем иметь дело далее при создании объектов классов. Это команды краткого создания объектов и массивов с помощью фигурных и квадратных скобок соответственно. И, наконец, это оператор function, создающий новую function, используемое как оператор. То есть
for(var i = 1; i<=controlsNumber; i++){
var curControl = eval("control" + i);
var reactionFunction = function(curValue){
var control = arguments.callee.control;
var result = curValue - control.oldValue;
trace("reaction: difference = " + result);
}
reactionFunction.control = curControl;
curControl.subscribe(reactionFunction);
// Тестируем:
curControl.setValue(4*i+1);
}
trace("------------------");
for(var i = 1; i<=controlsNumber; i++){
eval("control" + i).setValue(4*i + 1);
}
На выходе мы на сей раз получим:
control1.subscribe() called control1.setValue(5) called control1.raiseEvent() called reaction: difference = 2 control2.subscribe() called control2.setValue(9) called control2.raiseEvent() called reaction: difference = 4 control3.subscribe() called control3.setValue(13) called control3.raiseEvent() called reaction: difference = 6 control4.subscribe() called control4.setValue(17) called control4.raiseEvent() called reaction: difference = 8 control5.subscribe() called control5.setValue(21) called control5.raiseEvent() called reaction: difference = 10 ------------------ control1.setValue(5) called control1.raiseEvent() called reaction: difference = 0 control2.setValue(9) called control2.raiseEvent() called reaction: difference = 0 control3.setValue(13) called control3.raiseEvent() called reaction: difference = 0 control4.setValue(17) called control4.raiseEvent() called reaction: difference = 0 control5.setValue(21) called control5.raiseEvent() called reaction: difference = 0
Теперь все в порядке: мы устанавливаем в каждый контрол то значение, которое там и было, и в качестве разницы между старым и новым числами действительно получаем ноль.
Обратите еще раз внимание на то, что внутри цикла мы везде, где можно, поставили ключевое слово var. Казалось бы, до тех пор, пока оно не заключено в
В предыдущем параграфе перед нами стояла проблема: "пометить" каким-то образом разные экземпляры одной
Рассмотрим такой пример.
genFunc = function(str){
return function(quantity){
var tempStr;
for (var i=0; i<quantity; i++) tempStr += str;
return tempStr;
}
}
func = genFunc("_carriage_");
anotherFunc = genFunc("_truck_");
trace(func(3));
trace(anotherFunc(4));
Запустив его, мы получим строчки
_carriage__carriage__carriage_ _truck__truck__truck__truck_
(небольшой состав из трех вагонов, а за ним - из четырех грузовиков).
Получается, что с помощью genFunc мы сгенерировали другую func, которая занимается тем, что выводит строку, указанную нами при ее генерации, заданное количество раз. (А потом то же самое проделали еще раз, получив anotherFunc ).
Зачем такое может понадобиться? Первое, что приходит в голову: для реализации genFunc. Однако как же в этом случае решается проблема параметризации? Каким образом нам только что удалось "сделать одинаковые
На первый взгляд, все понятно. Кажется, будто бы значение аргумента str было подставлено на место, где встретилось слово str при генерации eval умел бы гораздо больше, как в JavaScript), а оператор function лишь создает новый объект, соответствующий этой genFunc вызывается, разумеется, уже в процессе выполнения, и об аргументах своих она узнает никак не ранее... Следующей в голову приходит другая идея, более основательная. Мы уже видели в параграфе о str (в данном случае - аргумент генерирующей genFunc вернет управление. Вроде бы вполне логичное объяснение, которое даже подтверждается вот таким примером:
genFunc = function(str){
var ret = function(quantity){
var tempStr;
for (var i=0; i<quantity; i++) tempStr += str;
return tempStr;
}
str = "_cart_";
return ret;
}
func = genFunc("_carriage_");
anotherFunc = genFunc("_truck_");
trace(func(3));
trace(anotherFunc(4));
Измененный таким образом код выведет
_cart__cart__cart_ _cart__cart__cart__cart_
То есть происходит обращение именно к измененному (уже после генерации возвращаемой str.
Таким образом, похоже на то, что действительно в процессе генерации объекта
genFunc = function(str){
var localStr = "_tank_";
var ret = function(quantity, name){
var tempStr;
for (var i=0; i<quantity; i++) tempStr += eval(name);
return tempStr;
}
localStr = "_cart_";
return ret;
}
func = genFunc("_carriage_");
anotherFunc = genFunc("_truck_");
trace('func(3, "str") =' + func(3, "str"));
trace('func(3, "localStr") =' + func(3, "localStr"));
trace('anotherFunc(4, "str") =' + anotherFunc(4, "str"));
trace('anotherFunc(4, "localStr") =' + anotherFunc(4,
"localStr"));
Он выдает на выходе
func(3, "str") =_carriage__carriage__carriage_ func(3, "localStr") =_cart__cart__cart_ anotherFunc(4, "str") =_truck__truck__truck__truck_ anotherFunc(4, "localStr") =_cart__cart__cart__cart_
На этот раз мы видим: несмотря на то, что имя переменной, хранящей повторяемую строку, мы задаем только при вызове сгенерированной
Единственное решение всех загадок, которые мы только что сами себе загадали, состоит вот в чем. Раз генерируемая genFunc в операторе return возвратила genFunc не будет уничтожен до тех пор, пока сгенерированный
Одна из наиболее показательных задач - это построение сложной
// Сначала определяем три генератора:
// сложение функций
_global.funcPlus = function(func1, func2){
return function(x){return func1(x) + func2(x);};
}
// умножение функций
_global.funcTimes = function(func1, func2){
return function(x){return func1(x)*func2(x);};
}
// композиция функций
_global.funcComposition = function(func1, func2){
return function(x){return func1(func2(x));};
}
// и формируем массивы генераторов и обычных функций
var operators_array = [funcPlus, funcTimes, funcComposition];
var functions_array = [function(x){return 1/x;}, Math.sin,
Math.exp];
// Массив instructions_array, передаваемый в следующую функцию,
// содержит указания пользователя - какие операции с какими
// функциями производить. Этот двумерный массив, содержит
// много субмассивов из двух элементов. Первый элемент -
// операция, второй - одна из функций.
_global.processInstructions = function(start_func, instruc-
tions_array){
var func = start_func;
for (var i=0; i<instructions_array.length; i++){
func = operators_array[instructions_array[i][0]](
func, functions_array[instructions_array[i][1]]
);
}
return func;
}
var start_func1 = function(x){return x*x;}
// Этот массив должен был бы генерироваться на основе
// действий пользователя
var instriction_set1 = [[1,1], [2,0]];
// Описывает функцию sin(1/x)/(x*x)
var resultFunc = processInstructions(start_func1,
instriction_set1);
trace("resultFunc(0.5) = " + resultFunc(0.5));
trace("resultFunc(0.1) = " + resultFunc(0.1));
// Протестируем, действительно ли сгенерировано
sin(1/x)/(x*x)
testFunc = function(x){return Math.sin(1/x)/(x*x);}
trace("testFunc(0.5) = " + testFunc(0.5));
trace("testFunc(0.5) = " + testFunc(0.1));
На выходе этой программы получаем:
resultFunc(0.5) = 3.63718970730273 resultFunc(0.1) = -54.402111088937 testFunc(0.5) = 3.63718970730273 testFunc(0.5) = -54.402111088937
Tо есть
Этот вопрос напоминает другой, о том, кто сильнее, слон или кит. Тем не менее, поразмышляем над ним.
Напоследок рассмотрим совсем необычный способ применения эффекта сохранения
_global.makePrivateField = function(getter, newGetterName, setter,
newSetterName){
var field;
this[newGetterName] = function(){
return getter.call(this, field);
}
this[newSetterName] = function(val){
field = setter.call(this, val);
}
}
car = {};
makePrivateField.call(
car,
function(wheelsN){ // Функция - getter
return "Wheels number = " + wheelsN;
},
"getWheelsNumber",
function(wheelsN){ // Функция - setter
return Math.round(wheelsN);
},
"setWheelsNumber"
);
car.setWheelsNumber(3.8);
trace(car.getWheelsNumber());
Возвращает такой код строчку
Wheels number = 4
Выведя в отладочное окно список переменных программы ( Ctrl+Alt+V ), мы получим следующее:
Global Variables:
Variable _global.makePrivateField = [function
'makePrivateField']
Level #0:
Variable _level0.$version = "WIN 6,0,21,0"
Variable _level0.car = [object #2, class 'Object'] {
getWheelsNumber:[function 'getWheelsNumber'],
setWheelsNumber:[function 'setWheelsNumber']
}
Мы видим, что в объекте car нет никаких видимых полей, зато появились
То, как ActionScript реализует
Самый простой способ определить
function func_name(arg1, arg2){
// здесь определяем тело функции
return ret_value; // возвращаем значение
}
То есть почти как в С - только вместо типа возвращаемого значения мы пишем ключевое слово function. Кроме того, не указываются типы аргументов. Чтобы вернуть из return. Если вслед за return сразу идет точка с запятой - получим аналог void из С, то есть никакое значение не возвращается. Наконец, можно вовсе не писать return. Словом, все как обычно.
А вот второй способ определить
func_name = function (arg1, arg2){
// здесь определяем тело функции
return ret_value; // возвращаем значение
}
То, что здесь написано, полностью эквивалентно первому func_name - это что-то вроде func_name является полем того клипа, в Actions которого мы определяем нашу function играет роль оператора генерации нужной нам
И действительно, в отличие от первого способа, второй можно применять внутри многих других конструкций языка, что предоставляет массу дополнительных возможностей использования. В частности, вы можете реализовать что-то вроде "условной трансляции" и " #include работает в процессе компиляции *.swf-файла, то на любой код, стоящий вокруг нее, она внимания не обращает. "Зацикливание" включаемых файлов приводит к генерации синтаксической ошибки и избавиться от этого зацикливания мы сможем только путем перекомпоновки включаемых файлов, никакие "условные включения" здесь не помогут.
Однако есть другие задачи, для которых в С++ часто используются директивы препроцессора. Первая из них - чтобы все
inc1_as = true;
_global.func = function(arg){
//code
}
При подключении обрамляем оператор #include условным оператором. При этом подключение произойдет в любой случае, но выполнен этот код будет только если выполнено условие проверки.
if (!inc1_as){
#include "inc1.as"
}
Вторая задача: иногда надо в зависимости от некоторого условия использовать тот или иной набор
В файле inc1.as пишем:
_global.func = function(arg){
//code1
}
В файле inc2.as пишем, в свою очередь:
_global.func = function(arg){
//code2 - отличие здесь
}
И внутри нашего флэш-ролика поступаем так:
if (!advancedMode){
#include "inc1.as"
}
else{
#include "inc2.as"
}
В обоих случаях только второй способ _global. То есть нам не придется заново подключать файл с
Однако, говоря об использовании оператора генерации . Различным вариантам такого использования посвящена вся эта лекция. Чтобы вы сразу увидели, что мы понимаем под "необычным" (для С++ и Java) использованием, взгляните на такой код.
iter2 = function(f){
return function(x){ return f(f(x)) }
}
trace(iter2(function(y){return y*y*y;})(2));
Этот код выводит число 512. Здесь есть два заслуживающих внимания момента: во-первых, мы написали
function progressionSum(low, hi, step){
for (i=low; i<=hi; i+=step) sum+=i;
// Здесь нарочно не пишем var как будто бы мы не знаем, что это
return sum;
}
// Используем нашу функцию
trace("sum = " + progressionSum(4, 10, 3));
//Вроде бы делаем то же самое
trace("sum = " + progressionSum(4, 10, 3));
Этот код выводит следующее:
sum = 21 sum = 42
То есть программист, решивший положиться на то, что Флэш сам заводит переменную, если в нее хотят что-то записать (и при этом делает ее равной нулю), должен хорошо представлять себе, где именно Флэш эту переменную заведет. В данном случае было заведено поле в том клипе, в котором определена progressionSum (и, конечно, записанное в нем значение сохранилось до следующего вызова). Так что var. В результате заводится обычная флэш-переменная, только теперь объект, в котором она лежит - это
Вот исправленный пример progressionSum:
function progressionSum(low, hi, step){
var sum;
for (i=low; i<=hi; i+=step) sum+=i;
return sum;
}
trace("sum = " + progressionSum(4, 10, 3));
trace("sum = " + progressionSum(4, 10, 3));
На сей раз мы получаем
sum = 21 sum = 21
как того и следовало ожидать. (В этом варианте
Однако не следует думать, что с var будет попросту проигнорировано). Точнее, все будет именно так, если код в кадре выполняется обычным образом - в тот момент, когда до этого кадра дошла очередь в линейке времени. Если же код из кадра был вызван при помощи команды Call (не путайте с методом Function.call, о котором далее), то переменная, объявленная с помощью var все-таки будет Call. Мы ведь уже говорили, что, в отличие от метода Function.call, команда Call - это типичный атавизм.
var везде, где только возможно (чтобы не засорять пространство имен объемлющего объекта). Например, в progressionSum из предыдущего примера все же была допущена ошибка, в чем мы убедимся, запустив такой код:
for (i=0; i<4; i++)
trace("i = " + i + "; sum = " + progressionSum(i, 10, 3));
В результате мы получим
i = 0; sum = 18
- всего одну строчку, хотя ожидали получить четыре. Ошибка, разумеется, в том, что в цикле for внутри
Так что следовало написать:
function progressionSum(low, hi, step){
var sum;
for (var i=low; i<=hi; i+=step) sum+=i;
return sum;
}
for (i=0; i<4; i++)
trace("i = " + i + "; sum = " + progressionSum(i, 10, 3));
и тогда мы получим
i = 0; sum = 18 i = 1; sum = 22 i = 2; sum = 15 i = 3; sum = 18
Хотелось бы, конечно, сделать и внешнюю переменную i
Итак, область видимости
i = 3;
function a(){
trace("function a: variable i = " + i);
var i=5;
trace("function a: variable i = " + i);
function b(){
trace("function b: variable i = " + i);
var i=7;
trace("function b: variable i = " + i);
}
trace("function a: variable i = " + i);
var i=6;
b();
trace("function a: variable i = " + i);
}
a();
Этот код выводит следующее:
function a: variable i = 3 function a: variable i = 5 function a: variable i = 5 function b: variable i = 6 function b: variable i = 7 function a: variable i = 6
Обратите внимание, что до тех пор, пока в b() не была объявлена собственная переменная i, использовалась переменная из a(), то есть из того b() была определена. (Но значение этой переменной бралось, разумеется, то, которое было на момент вызова, а не на момент b() снаружи, то она, так же, как и a(), использовала бы "внешнюю" переменную i, несмотря на то, что вызывалась бы внутри a(). Действительно, код
i = 3;
function b(){
trace("function b: variable i = " + i);
var i=7;
trace("function b: variable i = " + i);
}
function a(){
trace("function a: variable i = " + i);
var i=5;
trace("function a: variable i = " + i);
// Раньше здесь было определение b()
trace("function a: variable i = " + i);
var i=6;
b();
trace("function a: variable i = " + i);
}
a();
выводит
function a: variable i = 3 function a: variable i = 5 function a: variable i = 5
function b: variable i = 3 function b: variable i = 7 function a: variable i = 6
(Полужирным курсивом выделена строчка, в которой видно отличие от предыдущего примера.) По этой же причине попытка заменить большое количество похожих вызовов trace вызовом специальной
В самом деле, код
function traceI(funcName){
trace("function " + funcName + ": variable i = " + i);
}
i = 3;
function a(){
traceI("a");
var i=5;
traceI("a");
function b(){
traceI("b");
var i=7;
traceI("b");
}
traceI("a");
var i=6;
b();
traceI("a");
}
a();
и вовсе выводит
function a: variable i = 3 function a: variable i = 3 function a: variable i = 3 function b: variable i = 3 function b: variable i = 3 function a: variable i = 3
- очевидно, совсем не то, что мы хотели видеть. Но поделать ничего нельзя: i в фукнции a() и тем более b(), для traceI(funcName), определенной "снаружи", недоступны. Она видит только поле i того объекта, в котором помещен наш код.
Наконец, отметим, что и сама b() является своего рода "
Как обычно, начинаем с примера:
a();
function a(){
trace("function a called");
function b(){
trace("function b: first incarnation");
}
b();
function c(){
trace("function c called")
b();
function b(){
trace("function b: second incarnation");
}
b();
}
c();
b();
trace("--------------");
}
a();
b(); //Ничего не делает
Вас может слегка удивить, что первый раз а() вызвана до своего
function a called function b: first incarnation function c called function b: second incarnation function b: second incarnation function b: first incarnation -------------- function a called function b: first incarnation function c called function b: second incarnation function b: second incarnation function b: first incarnation --------------
Мы видим, что и первый вызов а() замечательно сработал! Также мы видим, что внутри с() всегда вызывается вторая инкарнация b(), а снаружи - первая. (Снаружи а() попытка вызвать b() вовсе не дает никакого результата). Таким образом, то, какая версия b() будет вызываться внутри другой а() и с() ) решается на этапе компиляции. Мы можем это представлять себе так: компилятор находит в коде все вызовы
a();
function a(){
trace("function a called");
function b(){
trace("function b: first incarnation");
}
b();
function c(){
trace("function c called")
b();
b = function(){
trace("function b: second incarnation");
}
b();
}
c();
b();
trace("--------------");
}
a();
b(); //Ничего не делает по-прежнему
дает на выходе
function a called function b: first incarnation function c called function b: first incarnation function b: second incarnation function b: second incarnation -------------- function a called function b: first incarnation function c called function b: first incarnation function b: second incarnation function b: second incarnation --------------
Вот так раз! Внутри с() мы добились того, что хотели - до определения второй инкарнации b() была вызвана первая. Но при этом эта первая инкарнация, определенная в вышележащей а(), была испорчена! (Заметим также, что снаружи а() вызвать b() по-прежнему не удается. Этот факт - обратная сторона того, что новая реализация записалась поверх старой и ее испортила. Старая-то была локальной! Если бы старой реализации не было вовсе - вот тогда запись b = function() означала бы создание новой переменной текущего клипа, содержащей в себе
И только если мы сделаем вторую инкарнацию b() по-настоящему локальной с помощью ключевого слова var (см. код, здесь это слово выделено жирным шрифтом):
a();
function a(){
trace("function a called");
function b(){
trace("function b: first incarnation");
}
b();
function c(){
trace("function c called")
b();
var b = function(){
trace("function b: second incarnation");
}
b();
}
c();
b();
trace("--------------");
}
a();
b(); //Ничего не делает по-прежнему
то наконец-то получим
function a called function b: first incarnation function c called function b: first incarnation function b: second incarnation function b: first incarnation -------------- function a called function b: first incarnation function c called function b: first incarnation function b: second incarnation function b: first incarnation
Итак, нам удалось сделать полностью
Вы наверняка помните, что среди небольшого количества типов данных, поддерживаемых во Флэш МХ, имеется тип . А это значит, что мы можем создавать объекты этого типа и переменные, ссылающиеся на эти объекты. Наконец, каждый тип данных во Флэш МХ предназначен для того, чтобы с ним особым образом работали некоторые операторы - для функционального типа это круглые скобки, оператор вызова
Итак, в некоторой переменной может лежать
Подтвердить такую точку зрения может использование нескольких
var a = function(){
trace("function a called");
}
var b = a;
a();
b();
(Переменные, в которых хранятся var всегда, когда это не противоречит вашим намерениям.)
Этот код, как и следовало ожидать, выводит:
function a called function a called
Здесь мы определяли
var d = c;
d();
function c(){
trace("function c called");
}
Этот код, хотя и не является интуитивно понятным, все же выводит именно
function с called
Мы еще раз убеждаемся в том, что
Про эту красивую возможность мы не раз уже говорили. Пора, наконец-то, привести простой и наглядный пример.
var d = ["a", "b", "c", "d"];
iterate(d, doubleString);
trace(d.join(", "));
iterate(d, capitalize);
trace(d.join(", "));
function iterate(arr, func){
for (var i=0; i<arr.length; i++) arr[i] = func(arr[i]);
}
function doubleString(str){
return str+str;
}
function capitalize(str){
return str.toUpperCase();
}
Этот код выводит
aa, bb, cc, dd AA, BB, CC, DD
В этом примере, кроме передачи _global (откуда они, напомним, доступны для любого _level, то есть всем загруженным *.swf-роликам. Причем, если нет конфликта имен, доступны без явного указания _global ). Поэтому мы, как правило, будем использовать именно
Мы уже видели, что любая ссылка может указывать на return. Получив эту ссылку, мы можем немедленно вызвать ту . Вот пример кода, который позволит ответить на наши вопросы.
t1 = "Снаружи функции";
function test(arg){
var t1 = "Внутри функции";
var func = function(){
trace("this.arg = " + this.arg);
trace("this.t1 = " + this.t1);
trace("t1 = " + t1);
}
trace("---- Вызываем func() ------");
func();
trace("------------------");
return func;
}
trace("======= Вызываем test()() ==============");
test()();
trace("");
trace("======= Вызываем а = test(); a() =======");
a = test("argString");
a();
На выходе получаем:
======= Вызываем test()() ============== ---- Вызываем func() ------ this.arg = this.t1 = Внутри функции t1 = Внутри функции ------------------ this.arg = this.t1 = t1 = Внутри функции ======= Вызываем а = test(); a() ======= ---- Вызываем func() ------ this.arg = argString this.t1 = Внутри функции t1 = Внутри функции ------------------ this.arg = this.t1 = Снаружи функции t1 = Внутри функции
Выводы отсюда можно сделать следующие.
this внутри arguments , о котором речь пойдет дальше: arguments, - это массив и аргументы лежат в нем по номерам). На этот-то объект, описывающий this в сгенерированной test()(); ), то test, поскольку и в том и в другом мы заводили переменную t1 ; а результат вызова test()(); говорит об отсутствии этой переменной в обсуждаемом объекте.Прием конструирования при передаче особенно удобен при сортировке массивов (мы его даже уже применяли, когда рассказывали о массивах):
var arr = ["aaaaaaaa", "bbb", "cc", "dddd", "e", "gg"];
arr.sort(
function(str1, str2){
return (str1.length > str2.length) ?
1 : ((str1.length == str2.length) ? 0 : -1);
}
);
trace(arr.join(", "));
В результате получаем:
e, cc, gg, bbb, dddd, aaaaaaaa
то есть мы отсортировали массив по возрастанию длины строк. В этом примере привлекает то, что вся информация сосредоточена в одном месте: прямо в вызове метода sort мы и указываем, как именно надо сортировать.
Сейчас мы покажем вам пример, из которого следует, насколько нужно быть осторожным при копировании а метод, вычисляющий сумму прогрессии по рекурсивному алгоритму, а потом пытаемся скопировать этот метод в другой объект.
a = {};
a.recProgressionSum = function(n){
if (n < 1) return n;
return n + this.recProgressionSum(n - 1);
}
trace("from a: " + a.recProgressionSum(5));
trace("from a: " + a.recProgressionSum(5.5));
b = {};
b.recFunction = a.recProgressionSum;
trace("from b: " + b.recFunction(5));
trace("from b: " + b.recFunction(5.5));
В результате выполнения этого кода мы получим следующее:
from a: 15 from a: 18 from b: 5 from b: 5.5
Причина такого b нет recProgressionSum, к которой имеется рекурсивное обращение. Идея явно указать объект, в котором эта а (или просто не запишем туда что-нибудь другое):
a = {};
a.recProgressionSum = function(n){
if (n < 1) return n;
return n + a.recProgressionSum(n - 1);
}
trace("from a: " + a.recProgressionSum(5));
trace("from a: " + a.recProgressionSum(5.5));
b = {};
b.recFunction = a.recProgressionSum;
trace("from b: " + b.recFunction(5));
trace("from b: " + b.recFunction(5.5));
a = {dddd: 1};
trace("from b: " + b.recFunction(5));
trace("from b: " + b.recFunction(5.5));
Этот код выводит
from a: 15 from a: 18 from b: 15 from b: 18 from b: 5 from b: 5.5
О том, как справиться с подобной бедой, мы узнаем в следующем параграфе.
Программисты, пишущие на С (а уж тем более - на ассемблере) хорошо знакомы с так называемым стеком вызовов. Под этот стек отводится специальная область памяти, в которой указываются адреса вызывающих и вызываемых , который определен внутри каждой
- это массив, содержащий в себе все аргументы имеет поле length, в котором записана его длина. Кроме того, имеет еще два поля: и , через которые можно получить доступ к объектам вызывающей и вызываемой для того, чтобы сделать
Вот пример такой
_global.joinBy = function(glue){
var tmpString = ((arguments[1] != undefined) ? arguments[1] :
"");
for (var i = 2; i<arguments.length; i++){
tmpString += glue + arguments[i];
}
return tmpString;
}
trace("result = " + joinBy("___"));
trace("result = " + joinBy("___", "r"));
trace("result = " + joinBy("___", "E", "R", "T", "G"));
Выводит этот код следующие бессмертные строки:
result = result = r result = E___R___T___G
Впрочем, то же самое можно сделать и чуть покороче (хотя, возможно, и менее наглядно):
_global.joinBy1 = function(glue){
if (arguments.length == 2) return arguments[1];
var tmpString = arguments.join(glue);
return tmpString.substr(tmpString.indexOf(glue) +
glue.length);
}
trace("result = " + joinBy1("___"));
trace("result = " + joinBy1("___", "r"));
trace("result = " + joinBy1("___", "E", "R", "T", "G"));
Результат получается тот же самый.
Обратите внимание, что - это самый настоящий массив, у которого, в частности, работает метод join. Хотя по этому поводу и могут возникнуть небольшие сомнения, когда Флэш МХ автоматически подсказывает нам поля и методы, которые можно выбрать у объекта - в этом списке только length, и . Но сомнения напрасны, все методы, относящиеся к массивам, прекрасно работают и с массивом . Его можно даже отсортировать, хотя не так уж просто представить себе задачу, в которой это было бы необходимо.
Как мы уже писали, поля и позволяют получить доступ к объектам вызывающей и вызываемой действительно дает доступ к объекту вызываемой ), поле дает доступ к объекту вызывающей , зато можно было бы п
олностью раскрутить стек вызовов ( и т.д.). К сожалению, дела обстоят вовсе не так радужно, как утверждает документация. И указывает именно на объект вызывающей вызывающей не очень широка. В следующих лекциях мы приведем пример использовании в одной из специфических форм наследования. А пока что - вот пример использования для отладочных целей. Если мы заранее позаботимся о снабжении каждой name, то отладочная
_global.traceDebugInfo = function(){
trace("---------------");
trace("function: " + arguments.caller.name);
trace("a = " + a);
trace("---------------");
}
_global.doSomething = function(){
a++;
traceDebugInfo();
}
_global.doSomething.name = "doSomething";
_global.doAnything = function(){
a+=4;
traceDebugInfo();
}
_global.doAnything.name = "doAnything";
a = 3;
traceDebugInfo();
doAnything();
doSomething();
doAnything();
Приведенный здесь код выводит
--------------- function: a = 3 --------------- --------------- function: doAnything a = 7 --------------- --------------- function: doSomething a = 8 --------------- --------------- function: doAnything a = 12 ---------------
Обратите внимание, что мы можем вызвать нашу traceDebugInfo() в любом месте - не только в специально приспособленной name.
Область применения поля , в отличие от , достаточно широка. Сейчас мы продемонстрируем, как "опасная превращается в "безопасную". Для разнообразия будет использована другая рекурсивная n!! - это произведение всех натуральных чисел той же четности, что и n, которые меньше или равны n ).
a = {};
a.factEvenOdd = function(n){
return n > 2 ? n*arguments.callee(n - 2) : n;
}
trace("7!! = " + a.factEvenOdd(7));
b = {};
b.specialFact = a.factEvenOdd;
a.factEvenOdd = function(){return "function is obsolete"};
trace("8!! = " + a.factEvenOdd(8));
trace("8!! = " + b.specialFact(8));
Код, который здесь написан, выводит в консоль следующее:
7!! = 105 8!! = function is obsolete 8!! = 384
Итак, использование позволило нам копировать рекурсивную
Объект типа имеет два интересных метода: и . Оба они служат одной цели: вызвать или . Аргументы самой и либо в массиве (для ), либо подряд через запятую (для ). Внутри же самой будет указывать на первый аргумент или . Привести примеры полезного использования этих методов мы пока не можем; методы эти нужны в основном при работе с наследованием, так что нужно подождать до соответствующей лекции (а самые информативные примеры будут приведены в лекции о множественном наследовании). Тем не менее, упомянем об использовании объекта совместно с этими методами.
Итак, иногда бывает нужно сделать
a = {};
func = function(text){trace(text + this.field);}
a.field = 5;
a.f1 = func;
a.f1("a = ");
delete a.f1;
Конечно, это громоздкий способ сделать простую вещь. И во Флэш МХ появился другой, гораздо более простой и элегантный. Он основан как раз на использовании метода объекта Function (не путайте со встроенной устаревшей процедурой call ). С помощью этого метода предыдущий пример можно переписать так:
a = {};
func = function(){trace(this.field);}
a.field = 5;
func.call(a, "a = "); // Применяем функцию к объекту а
Как мы видим, на первом месте стоит объект, к которому применяем , в который, кроме объекта, в качестве метода которого вызывается
obj1 = {dataStr: "Строка из первого объекта"};
obj2 = {dataStr: "Строка из второго объекта"};
_global.applyToAnother = function(func, obj, args){
trace("Вызвана функция applyToAnother");
// Отрезаем конец массива аргументов - берем все,
// кроме первых двух
return func.apply(obj, arguments.slice(2));
}
_global.traceDataStr = function(begStr, endStr){
trace(begStr);
trace(this.dataStr);
trace(endStr);
}
traceDataStr("Начало: объект _global", "Конец: объект _global");
trace("--------------");
applyToAnother(traceDataStr, obj1, "Начало: первый объект",
"Конец: первый объект");
trace("--------------");
applyToAnother(traceDataStr, obj2, "Начало: второй объект",
"Конец: второй объект");
Этот код выводит:
Начало: объект _global undefined Конец: объект _global -------------- Вызвана функция applyToAnother Начало: первый объект Строка из первого объекта Конец: первый объект -------------- Вызвана функция applyToAnother Начало: второй объект Строка из второго объекта Конец: второй объект
Примеры более осмысленного использования методов и вы, как и было обещано, найдете в лекции о наследовании (параграф про вызов
Чем параметр
Рассмотрим задачу создания множества похожих обработчиков событий для семейства родственных контролов. Поскольку мы пока не имеем необходимых знаний о настоящих Флэш-контролах, то создадим сейчас несколько объектов, ведущих себя как контролы (по крайней мере, с точки зрения клиентского кода). Дополнительное неудобство заключается в том, что и с тем, как делаются на Флэше классы, мы пока также не сталкивались. Тем не менее, все эти трудности преодолимы. И в результате их преодоления может родиться, например, вот такой код:
controlsNumber = 5;
for(var i = 1; i<=controlsNumber; i++){
set(
"control" + i,
{
name: "control" + i,
curValue: 2*i+1,
oldValue: 3*i,
setValue: function(newValue){
trace(this.name + ".setValue(" + newValue + ") called");
this.oldValue = this.curValue;
this.curValue = newValue;
this.raiseEvent();
},
eventReactionFunction: null,
subscribe: function(func){
trace(this.name + ".subscribe() called");
this.eventReactionFunction = func;
},
raiseEvent: function(){
trace(this.name + ".raiseEvent() called");
this.eventReactionFunction(this.curValue);
}
}
);
}
// Здесь начинается "клиентский" код
_global.reactionFunction = function(){
trace("reaction");
}
for(var i = 1; i<=controlsNumber; i++){
eval("control" + i).subscribe(reactionFunction);
}
trace("------------------");
for(var i = 1; i<=controlsNumber; i++){
eval("control" + i).setValue(4*i + 1);
}
Как мы видим, сгенерирован ряд объектов с именами, начинающимися со слова control. Эти объекты имеют subscribe, сохраняющую raiseEvent ).
В результате запуска этого кода на выполнение мы получим:
control1.subscribe() called control2.subscribe() called control3.subscribe() called control4.subscribe() called control5.subscribe() called ------------------ control1.setValue(5) called control1.raiseEvent() called reaction control2.setValue(9) called control2.raiseEvent() called reaction control3.setValue(13) called control3.raiseEvent() called reaction control4.setValue(17) called control4.raiseEvent() called reaction control5.setValue(21) called control5.raiseEvent() called reaction
Пока что реакция на события от всех контролов одинакова. И мы никак не можем ее изменить, не подписывая в качестве reactionFunction дела не решат: raiseEvent() передает в raiseEvent() мы считаем не подлежащим изменениям. Попробуем выкручиваться с помощью параметров.
Сначала сформулируем задачу до конца. Предположим, что raiseEvent() передает нам только текущее значение curValue. Мы могли бы воспользоваться тем, что приватных полей во Флэш МХ нет, поэтому oldValue. Однако для этого надо иметь ссылку на нужный контрол (или по крайней мере его имя). Попробуем сделать это вот так:
for(var i = 1; i<=controlsNumber; i++){
var curControl = eval("control" + i);
reactionFunction.control = curControl;
curControl subscribe(reactionFunction);
// Тестируем:
curControl.setValue(4*i+3);
}
На первый взгляд все неплохо. Теперь нужно информацию из параметра извлечь в самой :
_global.reactionFunction = function(curValue){
var control = arguments.callee.control;
var result = curValue - control.oldValue;
trace("reaction: difference = " + result);
}
В результате полный текст "клиентского" кода будет выглядеть так:
_global.reactionFunction = function(curValue){
var control = arguments.callee.control;
var result = curValue - control.oldValue;
trace("reaction: difference = " + result);
}
for(var i = 1; i<=controlsNumber; i++){
var curControl = eval("control" + i);
reactionFunction.control = curControl;
curControl.subscribe(reactionFunction);
// Тестируем:
curControl.setValue(4*i+1);
}
trace("------------------");
for(var i = 1; i<=controlsNumber; i++){
eval("control" + i).setValue(4*i + 1);
}
Запускаем исправленный код и получаем:
control1.subscribe() called control1.setValue(5) called control1.raiseEvent() called reaction: difference = 2 control2.subscribe() called control2.setValue(9) called control2.raiseEvent() called reaction: difference = 4 control3.subscribe() called control3.setValue(13) called control3.raiseEvent() called reaction: difference = 6 control4.subscribe() called control4.setValue(17) called control4.raiseEvent() called reaction: difference = 8 control5.subscribe() called control5.setValue(21) called control5.raiseEvent() called reaction: difference = 10 ------------------ control1.setValue(5) called control1.raiseEvent() called reaction: difference = -6 control2.setValue(9) called control2.raiseEvent() called reaction: difference = -2 control3.setValue(13) called control3.raiseEvent() called reaction: difference = 2 control4.setValue(17) called control4.raiseEvent() called reaction: difference = 6 control5.setValue(21) called control5.raiseEvent() called reaction: difference = 0
Сначала вроде бы все хорошо: значения контролов были установлены в 2*i+1, а мы ставим их в 4*i+1, так что разница должна составить 2*i, что мы и видим (она равна для контролов с первого по пятый четным числам от 2 до 10). Однако в самом конце у нас стоит еще один цикл установки параметров контролов - мы не стали его убирать. И правильно сделали, ибо в нем-то и проявляются наши ошибки. Несмотря на то что мы опять устанавливаем каждому контролу значение 4*i+1, то есть разница со старыми значениями должна быть 0 для всех контролов, на деле мы получаем иное. Возможно, вы с самого начала увидели в чем состоит ошибка - тем не менее, мы хотели продемонстрировать наглядно тот факт, что объект control этого объекта каждый раз было
ссылкой на control5. Вывод: нам нужны пять разных объектов
В языке AсtionScript есть всего четыре способа создавать новые объекты (не считая примитивные). Это оператор new, с которым мы будем иметь дело далее при создании объектов классов. Это команды краткого создания объектов и массивов с помощью фигурных и квадратных скобок соответственно. И, наконец, это оператор function, создающий новую function, используемое как оператор. То есть
for(var i = 1; i<=controlsNumber; i++){
var curControl = eval("control" + i);
var reactionFunction = function(curValue){
var control = arguments.callee.control;
var result = curValue - control.oldValue;
trace("reaction: difference = " + result);
}
reactionFunction.control = curControl;
curControl.subscribe(reactionFunction);
// Тестируем:
curControl.setValue(4*i+1);
}
trace("------------------");
for(var i = 1; i<=controlsNumber; i++){
eval("control" + i).setValue(4*i + 1);
}
На выходе мы на сей раз получим:
control1.subscribe() called control1.setValue(5) called control1.raiseEvent() called reaction: difference = 2 control2.subscribe() called control2.setValue(9) called control2.raiseEvent() called reaction: difference = 4 control3.subscribe() called control3.setValue(13) called control3.raiseEvent() called reaction: difference = 6 control4.subscribe() called control4.setValue(17) called control4.raiseEvent() called reaction: difference = 8 control5.subscribe() called control5.setValue(21) called control5.raiseEvent() called reaction: difference = 10 ------------------ control1.setValue(5) called control1.raiseEvent() called reaction: difference = 0 control2.setValue(9) called control2.raiseEvent() called reaction: difference = 0 control3.setValue(13) called control3.raiseEvent() called reaction: difference = 0 control4.setValue(17) called control4.raiseEvent() called reaction: difference = 0 control5.setValue(21) called control5.raiseEvent() called reaction: difference = 0
Теперь все в порядке: мы устанавливаем в каждый контрол то значение, которое там и было, и в качестве разницы между старым и новым числами действительно получаем ноль.
Обратите еще раз внимание на то, что внутри цикла мы везде, где можно, поставили ключевое слово var. Казалось бы, до тех пор, пока оно не заключено в
В предыдущем параграфе перед нами стояла проблема: "пометить" каким-то образом разные экземпляры одной
Рассмотрим такой пример.
genFunc = function(str){
return function(quantity){
var tempStr;
for (var i=0; i<quantity; i++) tempStr += str;
return tempStr;
}
}
func = genFunc("_carriage_");
anotherFunc = genFunc("_truck_");
trace(func(3));
trace(anotherFunc(4));
Запустив его, мы получим строчки
_carriage__carriage__carriage_ _truck__truck__truck__truck_
(небольшой состав из трех вагонов, а за ним - из четырех грузовиков).
Получается, что с помощью genFunc мы сгенерировали другую func, которая занимается тем, что выводит строку, указанную нами при ее генерации, заданное количество раз. (А потом то же самое проделали еще раз, получив anotherFunc ).
Зачем такое может понадобиться? Первое, что приходит в голову: для реализации genFunc. Однако как же в этом случае решается проблема параметризации? Каким образом нам только что удалось "сделать одинаковые
На первый взгляд, все понятно. Кажется, будто бы значение аргумента str было подставлено на место, где встретилось слово str при генерации eval умел бы гораздо больше, как в JavaScript), а оператор function лишь создает новый объект, соответствующий этой genFunc вызывается, разумеется, уже в процессе выполнения, и об аргументах своих она узнает никак не ранее... Следующей в голову приходит другая идея, более основательная. Мы уже видели в параграфе о str (в данном случае - аргумент генерирующей genFunc вернет управление. Вроде бы вполне логичное объяснение, которое даже подтверждается вот таким примером:
genFunc = function(str){
var ret = function(quantity){
var tempStr;
for (var i=0; i<quantity; i++) tempStr += str;
return tempStr;
}
str = "_cart_";
return ret;
}
func = genFunc("_carriage_");
anotherFunc = genFunc("_truck_");
trace(func(3));
trace(anotherFunc(4));
Измененный таким образом код выведет
_cart__cart__cart_ _cart__cart__cart__cart_
То есть происходит обращение именно к измененному (уже после генерации возвращаемой str.
Таким образом, похоже на то, что действительно в процессе генерации объекта
genFunc = function(str){
var localStr = "_tank_";
var ret = function(quantity, name){
var tempStr;
for (var i=0; i<quantity; i++) tempStr += eval(name);
return tempStr;
}
localStr = "_cart_";
return ret;
}
func = genFunc("_carriage_");
anotherFunc = genFunc("_truck_");
trace('func(3, "str") =' + func(3, "str"));
trace('func(3, "localStr") =' + func(3, "localStr"));
trace('anotherFunc(4, "str") =' + anotherFunc(4, "str"));
trace('anotherFunc(4, "localStr") =' + anotherFunc(4,
"localStr"));
Он выдает на выходе
func(3, "str") =_carriage__carriage__carriage_ func(3, "localStr") =_cart__cart__cart_ anotherFunc(4, "str") =_truck__truck__truck__truck_ anotherFunc(4, "localStr") =_cart__cart__cart__cart_
На этот раз мы видим: несмотря на то, что имя переменной, хранящей повторяемую строку, мы задаем только при вызове сгенерированной
Единственное решение всех загадок, которые мы только что сами себе загадали, состоит вот в чем. Раз генерируемая genFunc в операторе return возвратила genFunc не будет уничтожен до тех пор, пока сгенерированный
Одна из наиболее показательных задач - это построение сложной
// Сначала определяем три генератора:
// сложение функций
_global.funcPlus = function(func1, func2){
return function(x){return func1(x) + func2(x);};
}
// умножение функций
_global.funcTimes = function(func1, func2){
return function(x){return func1(x)*func2(x);};
}
// композиция функций
_global.funcComposition = function(func1, func2){
return function(x){return func1(func2(x));};
}
// и формируем массивы генераторов и обычных функций
var operators_array = [funcPlus, funcTimes, funcComposition];
var functions_array = [function(x){return 1/x;}, Math.sin,
Math.exp];
// Массив instructions_array, передаваемый в следующую функцию,
// содержит указания пользователя - какие операции с какими
// функциями производить. Этот двумерный массив, содержит
// много субмассивов из двух элементов. Первый элемент -
// операция, второй - одна из функций.
_global.processInstructions = function(start_func, instruc-
tions_array){
var func = start_func;
for (var i=0; i<instructions_array.length; i++){
func = operators_array[instructions_array[i][0]](
func, functions_array[instructions_array[i][1]]
);
}
return func;
}
var start_func1 = function(x){return x*x;}
// Этот массив должен был бы генерироваться на основе
// действий пользователя
var instriction_set1 = [[1,1], [2,0]];
// Описывает функцию sin(1/x)/(x*x)
var resultFunc = processInstructions(start_func1,
instriction_set1);
trace("resultFunc(0.5) = " + resultFunc(0.5));
trace("resultFunc(0.1) = " + resultFunc(0.1));
// Протестируем, действительно ли сгенерировано
sin(1/x)/(x*x)
testFunc = function(x){return Math.sin(1/x)/(x*x);}
trace("testFunc(0.5) = " + testFunc(0.5));
trace("testFunc(0.5) = " + testFunc(0.1));
На выходе этой программы получаем:
resultFunc(0.5) = 3.63718970730273 resultFunc(0.1) = -54.402111088937 testFunc(0.5) = 3.63718970730273 testFunc(0.5) = -54.402111088937
Tо есть
Этот вопрос напоминает другой, о том, кто сильнее, слон или кит. Тем не менее, поразмышляем над ним.
Напоследок рассмотрим совсем необычный способ применения эффекта сохранения
_global.makePrivateField = function(getter, newGetterName, setter,
newSetterName){
var field;
this[newGetterName] = function(){
return getter.call(this, field);
}
this[newSetterName] = function(val){
field = setter.call(this, val);
}
}
car = {};
makePrivateField.call(
car,
function(wheelsN){ // Функция - getter
return "Wheels number = " + wheelsN;
},
"getWheelsNumber",
function(wheelsN){ // Функция - setter
return Math.round(wheelsN);
},
"setWheelsNumber"
);
car.setWheelsNumber(3.8);
trace(car.getWheelsNumber());
Возвращает такой код строчку
Wheels number = 4
Выведя в отладочное окно список переменных программы ( Ctrl+Alt+V ), мы получим следующее:
Global Variables:
Variable _global.makePrivateField = [function
'makePrivateField']
Level #0:
Variable _level0.$version = "WIN 6,0,21,0"
Variable _level0.car = [object #2, class 'Object'] {
getWheelsNumber:[function 'getWheelsNumber'],
setWheelsNumber:[function 'setWheelsNumber']
}
Мы видим, что в объекте car нет никаких видимых полей, зато появились
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.