Московский физико-технический институт
Опубликован: 23.12.2005 | Доступ: свободный | Студентов: 2765 / 183 | Оценка: 4.61 / 4.44 | Длительность: 27:18:00
ISBN: 978-5-9556-0051-2
Лекция 5:

Функции

Параметризованные функции

Чем параметр функции отличается от ее аргумента? Ведь если от параметра зависит возвращаемое функцией значение (или побочные эффекты ее работы) - казалось бы, его тоже можно передать в качестве аргумента и дело с концом. Так оно, собственно, и есть - в тех случаях, когда код, вызывающий функцию, находится под вашим контролем. Если же вам нужно передать в чужой код (или даже в свой, но находящийся в другом, вполне законченном модуле) функцию определенной сигнатуры (то есть принимающую и возвращающую аргументы заранее заданных типов и в заранее заданном количестве), то все, что в эту сигнатуру не входит, как раз удобно передать в параметрах. Простейший пример: у вас есть модуль, строящий графики функций. Очевидно, в него следует передавать функцию, принимающую действительное число и возвращающую также действительное число. Если же вы хотите нарисовать несколько полиномов (например, четвертой степени, то есть имеющие пять коэффициентов), то вполне разумно будет написать код функции, вычисляющей полином один раз, а значения коэффициентов передавать через параметры. Другой пример - создание множества обработчиков событий - будет приведен в этом параграфе.

Задача: обработчики событий

Рассмотрим задачу создания множества похожих обработчиков событий для семейства родственных контролов. Поскольку мы пока не имеем необходимых знаний о настоящих Флэш-контролах, то создадим сейчас несколько объектов, ведущих себя как контролы (по крайней мере, с точки зрения клиентского кода). Дополнительное неудобство заключается в том, что и с тем, как делаются на Флэше классы, мы пока также не сталкивались. Тем не менее, все эти трудности преодолимы. И в результате их преодоления может родиться, например, вот такой код:

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);
}

На первый взгляд все неплохо. Теперь нужно информацию из параметра извлечь в самой функции реакции. Делаем это с использованием arguments.callee :

_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. Вывод: нам нужны пять разных объектов функции.