Js functions

Js functions


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;
};


Вроде бы всё — как всегда, исходники примеров скачать можно тут




Report Page