JavaScript
November 2011 21

JavaScript для чайников. Всё что вы хотели знать о функциях но боялись спросить

Как-то незаметно для себя, я решил отойти от возни с классами и паттернами, и разобраться с самыми обычными Js функциями. Думал, что будет достаточно скучно, но ошибся — оказалось очень даже интересно.

В этой статье я расскажу об особенностях объявления функций, и некоторых полезных паттернах (кхе-хе, да, они есть и тут)


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

Вроде бы всё — как всегда, исходники примеров скачать можно тут
+92
14.3k 405
Comments 43