Опубликован: 23.12.2005 | Уровень: специалист | Доступ: платный | ВУЗ: Московский физико-технический институт
Лекция 5:

Функции

"Безопасная" рекурсия

Область применения поля callee, в отличие от caller, достаточно широка. Сейчас мы продемонстрируем, как "опасная рекурсия ", описанная в прошлом параграфе, с помощью callee превращается в "безопасную". Для разнообразия будет использована другая рекурсивная функция, вычисляющая специальный вид факториала, обозначаемый обычно двумя восклицательными знаками. (Напомним, что 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

Итак, использование arguments.callee позволило нам копировать рекурсивную функцию в другой объект под новым именем и не заботиться о том, что произойдет в дальнейшем с исходной функцией.

Методы apply и call

Объект типа function имеет два интересных метода: apply и call . Оба они служат одной цели: вызвать функцию так, как будто она является методом объекта, передаваемого в первом аргументе apply или call . Аргументы самой функции передаются в следующих аргументах apply и call либо в массиве (для apply ), либо подряд через запятую (для call ). Внутри же самой функции при ее таком вызове мы как бы "ничего не заметим" - за исключением того, что ссылка this будет указывать на первый аргумент apply или call . Привести примеры полезного использования этих методов мы пока не можем; методы эти нужны в основном при работе с наследованием, так что нужно подождать до соответствующей лекции (а самые информативные примеры будут приведены в лекции о множественном наследовании). Тем не менее, упомянем об использовании объекта arguments совместно с этими методами.

Итак, иногда бывает нужно сделать функцию, которая (в числе других своих действий) вызывает заданную функцию для заданного объекта. Собственно говоря, для этого существуют целых два механизма - один более общий, но неудобный, и поэтому устаревший. И пришел он еще из Флэш 5. Во Flash 5 проблема с вызовом функции, не являющейся методом класса в таком контексте, где она им должна быть, решалась так. В нужном объекте просто заводили ссылку на нужную функцию. После чего можно было считать, что у объекта нужный метод появился. Затем ссылку удаляли:

a = {};
func = function(text){trace(text + this.field);}
a.field = 5;
a.f1 = func;
a.f1("a = ");
delete a.f1;

Конечно, это громоздкий способ сделать простую вещь. И во Флэш МХ появился другой, гораздо более простой и элегантный. Он основан как раз на использовании метода call объекта Function (не путайте со встроенной устаревшей процедурой call ). С помощью этого метода предыдущий пример можно переписать так:

a = {};
func = function(){trace(this.field);}
a.field = 5;
func.call(a, "a = "); // Применяем функцию к объекту а

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

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
Начало: второй объект
Строка из второго объекта
Конец: второй объект

Примеры более осмысленного использования методов call и apply вы, как и было обещано, найдете в лекции о наследовании (параграф про вызов функции с явным указанием базового класса).

алексеи федорович
алексеи федорович
Беларусь, рогачёв
Тамара Ионова
Тамара Ионова
Россия, Нижний Новгород, НГПУ, 2009