Оглавление цикла уроков по функциям в javascript.
Правильное понимание механизма работы функций в javascript — одно из самых важных качеств, необходимых опытным программистам на этом языке.
Варианты объявления функции
Обычно начинающие javascript-программисты пишут определение функции таким образом:
1 |
function someFunc () { ... } |
Но функции в Javascript — это объекты, а любые объекты можно присваивать переменной, в случае функции это может выглядеть так:
1 |
var someFunc = function () { ... }; |
Я не описался, поставив точку с запятой в конце, это нужно делать после любых присвоений, это ничем не отличается от var intVar = 1;. Да, конечно, javascript допускает запись без «;», размещая операторы каждый с новой строки, но не стоит этим злоупотреблять: если вы, например, захотите сжать код, удалив для начала все лишние пробелы и переносы строк, то непременно натолкнетесь на синтаксическую ошибку кода. Поэтому ставьте точку с запятой везде, где это необходимо.
А что, если мы не поставим «var» в начале? Как известно, это меняет область видимости переменной, она становится «глобальной», точнее, такое определение будет равнозначно следующему (в случае выполнения кода в браузере):
1 |
window.someFunc = function () { ... }; |
Чем же отличаются первое и второе определения функции? «Версия для начинающих» и «версия с var» почти идентичны, за исключением двух небольших нюансов. Во-первых, в случае, когда мы обходимся простой декларацией функции, она не только неявно присваивается переменной с таким же именем, но становится еще и именованной, то есть в поле name (мы помним, что функция — это объект) сохраняется ее имя:
1 2 |
function someFunc () { ... } alert(someFunc.name); // выведет "someFunc" |
На самом деле ничто не мешает нам создать именованную функцию и при втором варианте объявления, причем, имя переменной не обязано совпадать с именем функции:
1 2 |
var someFunc = function someFuncName () { ... }; alert(someFunc.name); // выведет "someFuncName" |
Более важное отличие состоит в том, что в первом случае функцию можно вызывать до ее объявления внутри контекста (хотя я рекомендую всегда объявлять функции до их использования) — это так называемое поднятие функций:
1 2 |
someFunc(); // все работает, функция поднимается в начало контекста function someFunc () { ... } |
сравним с этим:
1 2 |
someFunc(); // вызовет ошибку, функция определяется ниже var someFunc = function () { ... }; |
Локальные и глобальные функции
Следует еще учитывать, что при создании функций «простым» способом, они будут локальными, например:
1 2 3 4 5 |
function wrapFunc () { function someFunc () { ... } }; wrapFunc(); someFunc(); // вызовет ошибку, функция не определена |
Поможет в данном случае третий вариант определения:
1 2 3 4 5 |
function wrapFunc () { someFunc = function () { ... }; }; wrapFunc(); someFunc(); // теперь все в порядке, функция "глобальная" |
Либо можно сначала во внешнем контексте объявить переменную, а затем присвоить ей значение во внутреннем контексте, это поможет не засорять глобальный контекст:
1 2 3 4 5 |
var someFunc; function wrapFunc () { someFunc = function () { ... }; }; ... |
Но следующий код все равно вызовет ошибку, потому что хоть переменная и определена во внешнем контексте, внутри wrapFunc() она перекроется локальной переменной someFunc и внешняя переменная останется нетронутой, в ней не будет функции:
1 2 3 4 5 6 |
var someFunc; function wrapFunc () { function someFunc () { ... } // локальная переменная }; wrapFunc(); someFunc(); // снова ошибка, переменная пуста |
Передача функций как обычных переменных
Как уже говорилось выше, функция — это тоже объект, и этот объект легко можно, например, передать другой функции:
1 2 3 4 5 6 7 |
function someFunc () { alert("It works!"); }; function anotherFunc (func) { func(); }; anotherFunc(someFunc); // выведет окошко с надписью "It works!" |
Можно даже просто передать анонимную функцию, не присваивая ее переменной:
1 2 3 4 5 6 |
function anotherFunc (func) { func(); }; anotherFunc(function () { alert("It works!"); }); // выведет окошко с надписью "It works!" |
Замыкания
Естественно, функцию можно и вернуть из другой функции. Причем, возвращаемая функция будет иметь доступ ко всем переменным, доступным внутри контекста, где она была определена (локальным и внешним). Это крайне полезное свойство называется замыканием. Вот пример:
1 2 3 4 5 6 7 8 |
function factory () { var text = "Test"; return function () { alert(text); }; } var func = factory(); // вернет функцию func(); // выведет "Test" |
В этом примере видно, что хотя переменная text и определена как локальная в функции factory(), она доступна для возвращаемой функции внутри созданного для нее замыкания. Замыкания — незаменимая вещь при работе с обработчиками событий, асинхронных ajax-запросах и т.п.
Переменная arguments
Напоследок упомяну, что когда вы вызываете функцию, в контексте ее выполнения автоматически создается переменная arguments, содержащая, как нетрудно догадаться, список аргументов, переданных функции, причем, их количество не обязано совпадать с количеством параметров, указанных в декларации функции. Переменная arguments выглядит почти как массив, аргументы доступны по ключам от 0 до n-1 (где n — количество переданных аргументов), даже имеет поле length, но на самом деле это не массив, например, у нее нет метода join() и других. Кроме того, у arguments задано поле callee, содержащее ссылку на выполняемую в данный момент функцию (она ведь объект :)). Проиллюстрируем на примере:
1 2 3 4 5 |
function someFunc () { // параметры вообще не указаны alert(arguments[0]); // выведет "Test" alert(arguments.callee.name); // выведет "someFunc" } someFunc("Test"); |