Объявление функции
Если не обращать внимание на всякие извращения, то объявить функцию можно всего двумя способами:
//создать именованную функцию function a() { console.log(1); } //создать анонимную функцию, а затем присвоить ее переменной b = function() { console.log(2); }
Разница между ними кажется небольшой, но это только кажется.
Что будет если выполнить этот код?
console.log( "sample b" ); b(); function b() { console.log(1); }
правильно — в консоли появится сначала текст с названием примера, а потом единичка, потому что все именованные функции переносятся в начало области видимости, и вызывать их можно когда угодно.
Усложняем задачу
console.log( "sample c" ); function c() { console.log(1); } c(); function c() { console.log(2); }
Но нас ведь так просто не запутать. Функции переносятся в начало ровно в том порядке, в котором они были созданы, то есть в консоли будет 2.
Ладно, а так?
console.log( "sample d" ); d(); var d = function() { console.log(2); }
Ответ: а тут правила другие — это вообще не заработает, потому что значение переменной на момент вызова — undefined.
А если так?
console.log( "sample e" ); var e = function(){ console.log(1); } e(); e = function() { console.log(2); }
Ну тут уже все ясно, да — на момент вызова е содержит в себе первую функцию, так что в консоли будет единичка.
А теперь настало время для самого сложного вопроса *барабанная дробь*
А что будет тут ?
console.log( "sample f" ); var f = function() { console.log(1); } f(); function f(){ console.log(2); } f();
правильный ответ: два раза единичка. После своего объявления, переменные перекрывают функции.
Всегда.
Immediate functions
За этим названием скрываются «одноразовые» функции — они не имеют имени, и выполняются сразу после своего объявления.
Зачем это нужно? По большей части за тем, чтобы не засорять глобальное пространство имен. Например, если надо проинициализировать что-нибудь, и при инициализации нам потребуется парочка переменных которые потом вообще совсем не нужны.
Объявить такую функцию можно двумя, по сути, эквивалентными способами.
console.log("//immidiate function"); //sample a (function(){ console.log( "a" ); })(); //sample b (function(){ console.log( "b" ); }());
Отличий между ними нет никаких.
Init time branching
Иногда случается, что значение функции зависит от какого-нибудь значения, которое после инциализации не меняется. ну что-нибудь вроде этого:
// ПЛОХОЙ пример function saySomethingClever(){ var appleTest = /Apple/i; var googleTest = /Google/i; if( appleTest.test(navigator.vendor) ) console.log("I love apples <3") else if( googleTest.test(navigator.vendor) ) console.log("android is everything for me <3") else console.log("i love this unpopular corporation too") } saySomethingClever();
Все здорово, кроме того, что каждый раз при вызове мы делаем ставшую уже абсолютно ненужной проверку. Переписать функцию можно так:
// Хороший пример var saySomethingClever = (function(){ var appleTest = /Apple/i; var googleTest = /Google/i; if( appleTest.test(navigator.vendor) ) return function(){ console.log("I love apples <3"); } if( googleTest.test(navigator.vendor) ) return function(){ console.log("android is everything for me <3"); } return function(){ console.log("i love this unpopular corporation too"); } })(); saySomethingClever();
Как мог заметить внимательный читатель, здесь используется ещё и immidiate function. Теперь проверка выполняется ровно один раз.
Self defining function
Иногда бывает так, что при первом вызове функции нужно выполнить какие-нибудь дополнительные действия. Реализовать это можно следующим образом:
var selfDefining = function() { console.log("some really heavy initialization occured"); console.log("f*ck yeah!"); selfDefining = function(){ console.log("job done!"); } } selfDefining(); selfDefining();
Такой прием помогает сэкономить одну переменную за пределами функции, по которой мы бы проверяли вызывалась функция или нет.
Currying
С помощью этого приема можно создать частный вариант какой-нибудь довольно общей функции. Реализация выглядит так:
function curry( fn ){ var slice = Array.prototype.slice, storedArgs = slice.call( arguments, 1 ); return function() { var args = storedArgs.concat( slice.call( arguments ) ); return fn.apply( this, args ); } }
Пока конечно нифига непонятно, но это нормально. Сейчас все объясню.
Допустим у нас есть функция которая печатает сообщение.
function printMessage( author, message ){ console.log( author + " say: " + message ) }
Но в данном модуле проекта в качестве значения author всегда используют me, так что нам очень желательна более частная её версия принимающая только строчку с сообщением, а автора вписывающая сама.
var printMyMessage = curry( printMessage, "me" ); printMyMessage( "I would like to tell you about birds and bees in js world" );
И теперь она у нас есть.