# Domknięcia (Closures)
Spójrzmy na ten kod:
function outerFunc() {
let outerVar = 'I am outside!';
function innerFunc() {
console.log(outerVar); // => "I am outside!"
}
innerFunc();
}
2
3
4
5
6
7
8
9
W zakresie (eng. scope) innerFunc mamy dostęp do outerVar dzięki leksykalnemu
zakresowi (eng. lexical scope).
Zauważmy, że inwokacja innerFunc znajduje się w jej leksykalnym zakresie (zakres funkcji outerFunc).
Spróbujmy teraz wykonać innerFunc poza jej leksykalnym zakresem.
function outerFunc() {
let outerVar = 'I am outside!';
function innerFunc() {
console.log(outerVar); // => "I am outside!"
}
return innerFunc;
}
const myFunc = outerFunc();
myFunc(); // => "I am outside!"
2
3
4
5
6
7
8
9
10
11
12
13
14
Co ważne innerFunc nadal ma dostęp do outerVar z jej leksykalnego zakresu nawet gdy jest wywołana spoza
niego.
# Przykłady:
# Event handler
let countClicked = 0;
myButton.addEventListener('click', function handleClick() {
countClicked++;
myText.innerText = `You clicked ${countClicked} times`;
});
2
3
4
5
6
Gdy naciskamy przycisk, handleClick jest wykonywana gdzieś w kodzie DOM. Wykonanie
dzieje się z dala od miejsca definicji. Lecz handleClick będąc domknięciem zapamiętuje conuntClicked
i odświeża go przy każdym kliknięciu.
# Iterator funkcji forEach
let countEven = 0;
const items = [1, 5, 100, 10];
items.forEach(function iterator(number) {
if (number % 2 === 0) {
countEven++;
}
});
countEven; // => 2
2
3
4
5
6
7
8
9
10
iterator jest domknięciem ponieważ zapamiętuje zmienną countEven.
# Currying
function multiply(a) {
return function executeMultiply(b) {
return a * b;
}
}
const double = multiply(2);
double(3); // => 6
double(5); // => 10
const triple = multiply(3);
triple(4); // => 12
2
3
4
5
6
7
8
9
10
11
12
multiply jest currieowaną funkcją, która zwraca kolejną funkcję, currying jest możliwy dzięki domknięciom
executeMultiply(b) jest domknięciem, które zapamiętuje a ze swojego leksykalnego zakresu
i przy wykonaniu kalkuluje wartość a + b
# setTimeout
for(var i = 1; i < 5; i++){
setTimeout(() => {
console.log(i)
}, i)
}
2
3
4
5
6
7
Celem jest wyświetlenie numerów 1, 2, 3 i 4.
Jednak wynikiem jest 4krotne wypisanie 5tki, występuje tutaj problem z domknięciem, zmienna i jest
zdefiniowana w pętli for i wszystkie wewnętrzne funkcje mają do niej dostęp więc po wykonaniu pętli
wszystkie wewnętrzne funkcje odwołują się do tej samej zmiennej i, która w ten czas ma wartość 5.
Możemy to rozwiązać zmieniając var na let lub jeśli nie mamy możliwości używania ES6
możemu użyć IIFE (immediately-invoked function expression).
for(var i = 1; i < 5; i++){
(()=>{
var j = i;
return setTimeout( () =>{
console.log(j)
}, j * 1000)
})()
}
2
3
4
5
6
7
8
# Prywatność danych
Rozważmy kod:
function privateHolder(value){
return {
get: () => value
}
}
const myHolder = privateHolder(10);
console.log(myHolder.get()) // => 10
2
3
4
5
6
7
8
9
10
W JavaScript, domknięcia są mechanizmem używanym do tworzenia prywatnych danych.
Przy użyciu domknięć, domknięte zmienne są dostępne jedynie w jej leksykalnym zakresie
i nie można się do nich dostać spoza tego zakresu bez użycia uprzywilejowanych metod jak np. get() w privateHolder().
# Kontekst wywołania (Execution context):
Kontekst wywołania to środowisko, którym wykonywany jest kod
JavaScriptowy, to w nim definiowane są wartości this, zmiennych, obiektów i funkcji.
# Rodzaje
Rozróżniamy 3 typy execution contextu w JavaScripcie:
Global execution context (GEC): Defaultowy EC tworzony w momencie gdy plik ładowany jest przez przeglądarkę.
GEC zawiera cały kod, który nie znajduje się w środku funkcji i obiektów. Jako że silnik
JS jest jednowątkowy istnieje tylko jeden GEC.
Functional execution context (FEC): Lokalny EC tworzony za każdym razem gdy wykonywana jest funkcja.
Każda funkcja ma swój EC. Gdy wykonując GEC silnik JS napotka wywołanie funkcji
stworzy nowy FEC dla tej funkcji.
Eval: Execution context w środku funkcji
eval
@TODO add info about execution stack