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

Функции

"Иерархия" локальных переменных

Итак, область видимости локальных переменных не зависит от вложенных блоков. Но... зависит от вложенных функций! Вот пример:

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

Итак, нам удалось сделать полностью локальные функции, да еще и определяемые в нужный момент. Однако, чтобы объяснить, как же это на самом деле у нас получилось, придется ввести понятие функции как объекта. Этому и посвящен следующий параграф.