понедельник, 5 января 2009 г.

JavaScript: используйте arguments.callee вместо названия функции

Часто разработчики, когда им нужно изнутри функции сослаться на саму эту функцию, используют её имя, заявленное при её объявлении. Например, так:
var factorial = function(x){
    return x == 1 ? 1 : x + factorial(--x);
}
В данном коде мы, фактически, обращаемся к внешней цепочке областей видимости и читаем из неё переменную "factorial" и, надеясь, что это - функция, вызываем её.

Проблема заключается в том, что JavaScript - язык, в высшей степени чреватый конфликтами имён в случае, если над разработкой одного и того же функционала работает не один, а несколько человек. И в этой ситуации нельзя быть до конца уверенным, что переменной "factorial" в какой-то момент кто-нибудь не решит присвоить другую функцию или даже какое-нибудь значение типа числа, при этом функция может быть, скажем, записана в другую переменную и по этой причине разработчик, использующий эту функцию, может думать, что всё впорядке и функция должна работать:
var fac = factorial;
factorial = 27;
alert(fac(factorial));

Но функция выдаст ошибку:
factorial is not a function
factorial(26)test.js (line 2)
Просто нужно помнить, что функции в JavaScript - это такой особый тип данных, а не неизменяемая часть программы, как, например, в Java. Функцию можно присваивать любой переменной, свойству объекта или элементу массива, жонглируя ей как угодно. Эта возможность фактически означает, что у функций в JavaScript нет своих постоянных имён! Поэтому для того, что бы не лишать программистов этой возможности, лучше всегда изнутри функции ссылаться на неё с помощью встроенного и поддерживаемого свойства псевдо-массива Arguments, называемого "callee". Это свойство всегда ссылается на функцию, которая выполняется в данный момент, поэтому вы оказываетесь застрахованы от недоразумений, продемонстрированных выше:
function factorial(x) {
    return x == 1 ? 1 : x + arguments.callee(--x);
}
var fac = factorial;
factorial = 27;
alert(fac(factorial)); // Возвращает "378".
Но в JavaScript всё-таки есть способ элегантно обойти данную ситуацию. Если вам не очень нравится прибегать к вызову свойства callee объекта Arguments, то можно воспользоваться конструкцией, называемой "литералом функции" с необязательным параметром, содержащим имя функции:
var factorial = function f(x) {
    return x == 1 ? 1 : x + f(--x);
};
alert(f); // Ошибка: "f is not defined"
В этом коде не создаётся внешней переменной "f", здесь функция, как и в предыдущем примере, присваивается только внешней переменной "factorial", а переменная "f" определяется только внутри области видимости функции и служит для ссылки на данную функцию, так же, как и "arguments.callee", так что можно без опаски использовать переменную "f" для своих нужд - на работу данной функции это никак не повлияет.

Минус этого элегантного приёма состоит в том, что такие трюки, к сожалению, увеличивают требования к разработчикам, поддерживающим код. За JavaScript незаслуженно закрепилась репутация простого и чуть ли не детского языка программирования и поэтому когда разработчики сталкиваются с его нетривиальными приёмами, то скорее склонны раздражаться на того разработчика, который их использовал, чем менять давно и прочно сложившийся стереотип и глубже изучать этот интересный язык.