Podstawy Programowania Funkcyjnego #2 - domknięcia i higher order functions

W tej serii uczymy się Podstaw Programowania Funkcyjnego. Wykorzystamy przy okazji wiedzę zdobytą w serii dotyczącej ES6. Tym razem zmierzymy się z postrachem początkujących programistów JavaScript, czyli domknięciami. Udowodnię że na pewno już się z nimi spotkałeś i wcale nie są trudne do zrozumienia. Zaczynajmy!

# Czym jest domknięcie - krótka definicja

Domknięcie (Closure), to zagadnienie, którym doświadczeni programiści wręcz straszą początkujących. Wszędzie mówi się że to pytanie pojawia się zawsze na rozmowie kwalifikacyjnej. Jednak nie należy się tego bać, ponieważ domknięcie jest bardzo proste do zrozumienia, co więcej - jeśli już pisałeś jakiś kod w JavaScript, to z pewnością zetknąłeś się już z Closures nieświadomie.

Jakiś czas temu na twitterze pojawił się challenge, który polegał na wytłumaczeniu czym jest domknięcie za pomocą limitu znaków jednego tweeta. Teraz jest to skarbnica definicji, pozwalająca zrozumieć to zagadnienie w bardzo łatwy sposób, dlatego przytoczę jedno z nich.

"Statefull function" , czyli funkcja ze stanem. Co to oznacza?

Czyste funkcje, które wspominaliśmy w poprzedniej części podstaw programowania funkcyjnego, to funkcje bez stanu, czyli funkcje, które nie modyfikują zmiennych spoza swojego bloku. Domknięcie, możemy zaobserwować wtedy, kiedy funkcja ma dostęp do zmiennej spoza swojego bloku, czyli zmiennej globalnej, bądź zmiennej w funkcji nadrzędnej.

 
function outer() {
	var x = 7;

	function inner() {
		console.log( x ); // 7
	}

	inner();
}

outer();

W powyższym fragmencie kodu widzimy domknięcie, ponieważ wewnętrzna funkcja inner ma dostęp do zmiennej x.

# Czym na prawdę jest domknięcie?

Możemy teraz bardziej rozwinąć wcześniejszą definicję i pokazać czym na prawdę jest domknięcie.

Domknięcie możemy zaobserwować wtedy, gdy funkcja pamięta i ma dostęp do swojego zasięgu zmiennych, nawet jeśli jest wywołana pozna nim

 
function outer() {
	var x = 7;

	function inner() {
		console.log( x );
	}

	return inner;
}

var closure = outer();
closure(); // 7

Funkcja outer() zwraca funkcję inner(), która ma swój zasięg w całej funkcji outer(). Przypisujemy ją do zmiennej closure, więc wywołujemy funkcję inner() poza jej zasięgiem, a ona nadal ma dostęp do zmiennej "x".

W rzeczywistości funkcje, które nie zostaną już wykorzystane zostają usunięte z pamięci poprzez Garbage Collector, jednak w tym przypadku sytuacja jest inna. Funkcja outer() nie zostanie usunięta, ponieważ funkcja closure() cały czas będzie potrzebowała do niej dostęp.

via GIPHY

Do czego przydatne są domknięcia? Pozwalają na wyodrębnienie funkcji i zmiennych spoza zasięgu globalnego, co daje nam zmienne prywatne.

# Funkcje zwracające inne funkcje

Kolejny przykład pozwalający pokazać domknięcie, to Higher Order Functions, czyli funkcje zwracające inne funkcje:

 
function outer() {
	return function inner(x){
		console.log(x);
	};
}

var higherOrder = outer();
higherOrder(5); // 5

Dlaczego jest to możliwe? Ponieważ w języku JavaScript funkcje mogą być traktowane jako wartości.

Jednak Higher Order Functions mogą także przyjmować funkcje jako argumenty:

 
function hof(x,fn) {
	fn(x);
}

function add2(val){
	console.log( val + 2 );
}

hof(2, add2); //4

Domknięcie sprawia, że funkcja pamięta swój zasięg i zmienne, ale również może pamiętać wartość przypisanej zmiennej.

 
function add(x) {
	return function sum(y){
		return x + y;
	}
}

var add10 = add(10);
var add20 = add(20);

add10(2); //12
add10(5); //15
add20(3); //23

Przypisując zmienną add10 do funkcji z argumentem add(10); otrzymujemy funkcję sum(), która pamięta swój zasięg i zmienną x, w której mamy wpisaną wartość 10. Przy kolejnych wywołaniach add10 podajemy tylko argument funkcji sum.

W serii o ES6, pisząc na temat arrow functions, skorzystaliśmy z przykładu użycia funkcji filter, który również możemy nazwać Higher Order Function, ponieważ przyjmuje inną funkcję jako argument.

 
var HitMan = characters.filter(ev => ev.role == 'Hitman');

W powyższym przykładzie funkcja filter jako argument przyjmuje funkcje anonimową z argumentem ev.

blog comments powered by Disqus