Беларусь, рогачёв |
Функции
"Безопасная" рекурсия
Область применения поля 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 вы, как и было обещано, найдете в лекции о наследовании (параграф про вызов функции с явным указанием базового класса).