Как-то незаметно для себя, я решил отойти от возни с классами и паттернами, и разобраться с самыми обычными Js функциями. Думал, что будет достаточно скучно, но ошибся — оказалось очень даже интересно.
В этой статье я расскажу об особенностях объявления функций, и некоторых полезных паттернах (кхе-хе, да, они есть и тут)
Если не обращать внимание на всякие извращения, то объявить функцию можно всего двумя способами:
Разница между ними кажется небольшой, но это только кажется.
правильно — в консоли появится сначала текст с названием примера, а потом единичка, потому что все именованные функции переносятся в начало области видимости, и вызывать их можно когда угодно.
Но нас ведь так просто не запутать. Функции переносятся в начало ровно в том порядке, в котором они были созданы, то есть в консоли будет 2.
Ответ: а тут правила другие — это вообще не заработает, потому что значение переменной на момент вызова — undefined.
Ну тут уже все ясно, да — на момент вызова е содержит в себе первую функцию, так что в консоли будет единичка.
А теперь настало время для самого сложного вопроса *барабанная дробь*
правильный ответ: два раза единичка. После своего объявления, переменные перекрывают функции.
Всегда.
За этим названием скрываются «одноразовые» функции — они не имеют имени, и выполняются сразу после своего объявления.
Зачем это нужно? По большей части за тем, чтобы не засорять глобальное пространство имен. Например, если надо проинициализировать что-нибудь, и при инициализации нам потребуется парочка переменных которые потом вообще совсем не нужны.
Объявить такую функцию можно двумя, по сути, эквивалентными способами.
Отличий между ними нет никаких.
Иногда случается, что значение функции зависит от какого-нибудь значения, которое после инциализации не меняется. ну что-нибудь вроде этого:
Все здорово, кроме того, что каждый раз при вызове мы делаем ставшую уже абсолютно ненужной проверку. Переписать функцию можно так:
Как мог заметить внимательный читатель, здесь используется ещё и immidiate function. Теперь проверка выполняется ровно один раз.
Иногда бывает так, что при первом вызове функции нужно выполнить какие-нибудь дополнительные действия. Реализовать это можно следующим образом:
Такой прием помогает сэкономить одну переменную за пределами функции, по которой мы бы проверяли вызывалась функция или нет.
С помощью этого приема можно создать частный вариант какой-нибудь довольно общей функции. Реализация выглядит так:
Пока конечно нифига непонятно, но это нормально. Сейчас все объясню.
Допустим у нас есть функция которая печатает сообщение.
Но в данном модуле проекта в качестве значения author всегда используют me, так что нам очень желательна более частная её версия принимающая только строчку с сообщением, а автора вписывающая сама.
И теперь она у нас есть.
UPD. Дополнение к статье от одного очень хорошего человека с ником jamayka
Например, у нас есть тупо факториал, который используется в куче кода:
ну или
Потом по каким-то причинам нам нужно
В итоге получается, что и вызовы factorial(5) и realFactorial(5) вываливают ошибку. Это получается, т.к. рекурсивный вызов factorial использует переменную из родительской области видимости, а там она переопределяется. В этом случае надо определять функцию так:
Ну или чтобы не путаться:
Вроде бы всё — как всегда, исходники примеров скачать можно тут
В этой статье я расскажу об особенностях объявления функций, и некоторых полезных паттернах (кхе-хе, да, они есть и тут)
1. Объявление функции
Если не обращать внимание на всякие извращения, то объявить функцию можно всего двумя способами:
//создать именованную функцию
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" );
И теперь она у нас есть.
UPD. Дополнение к статье от одного очень хорошего человека с ником jamayka
Объявление рекурсивных функций
Например, у нас есть тупо факториал, который используется в куче кода:
var factorial = function (n) {
return n === 1 ? 1 : factorial(n - 1) * n;
};
ну или
function factorial(n) {
return n === 1 ? 1 : factorial(n - 1) * n;
};
Потом по каким-то причинам нам нужно
var realFactorial = factorial;
factorial = function (n) {
throw 'please use realFactorial instead';
};
В итоге получается, что и вызовы factorial(5) и realFactorial(5) вываливают ошибку. Это получается, т.к. рекурсивный вызов factorial использует переменную из родительской области видимости, а там она переопределяется. В этом случае надо определять функцию так:
var factorial = function factorial (n) {
return n === 1 ? 1 : factorial(n - 1) * n;
};
Ну или чтобы не путаться:
var factorial = function f (n) {
return n === 1 ? 1 : f(n - 1) * n;
};
Вроде бы всё — как всегда, исходники примеров скачать можно тут