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

Функции

Принцип сохранения контекста

В предыдущем параграфе перед нами стояла проблема: "пометить" каким-то образом разные экземпляры одной функции, чтобы они в результате работали с разными объектами. В качестве места, где можно расположить индивидуальную информацию, связанную с данной копией функции, мы выбрали объект, представляющий эту функцию. Казалось бы, какие еще варианты могут быть? И тем не менее, эти дополнительные варианты существуют. И сейчас мы с ними познакомимся.

Возвращаем функцию из другой функции

Рассмотрим такой пример.

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_

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