Lexical scope
● Zasięg zmiennych jest określany na podstawie ich umiejscowienia w kodzie i kontekstu w jakim się znajdują
● Scope zmiennych jest stały, określany na etapie kompilacji (wczesne wiązanie)
● W JavaScripcie scope jest określany poprzez funkcje (ES5) oraz bloki kodu (ES6)
Lexical scope
● Kod wewnątrz scope'a ma dostęp do zmiennych zadeklarowanych w scopie zewnętrznym
● W pierwszej kolejności wykorzystywane są zmienne w lokalnym zasięgu, następnie kolejne poziomy zagnieżdzenia scope'a
const a = 1;const b = 2;
function x() { const b = 3; console.log(a); // => 1 console.log(b); // => 3}
Function scope
● Jedyny dostępny sposób określania zasięgu zmiennych w ES5
● Najmniejszą jednostką scope'a jest funkcja
● Mechanizm specyficzny dla JavaScriptu
● Do deklaracji zmiennej wewnątrz scope'a służy słowo kluczowe var
● Użycie var w ES6+ jest niezalecane
Czym jest hoisting?
● Dwie fazy przetwarzania kodu - kompilacja i wykonanie
var a = 1;
var myFunction = function () { // code...};
function myOtherFunction() { // code...}
myFunction();myOtherFunction();
Czym jest hoisting?● W fazie kompilacji wczytywane są
jedynie deklaracje (zarówno zmiennych jak i funkcji)
var a;
var myFunction;
function myOtherFunction() { // code...}
a = 1;
myFunction = function () { // code...};
myFunction();myOtherFunction();
● W fazie wykonywania kodu przetwarzane są pozostałe instrukcje - przypisania zmiennych, wywołania funkcji itp
Hoisting - minusy
● Hoisting deklaracji zmiennych może prowadzić do nieczytelnego, nieoczywistego kodu
var a = 1;
function doSomeStuff() { a = 2; var a = 3;
return a;}
console.log(doSomeStuff()); // => 3console.log(a); // => 1
Hoisting - plusy● Hoisting funkcji pozwala na czytelniejszy zapis kodu - funkcje zawierające główną
logikę można umieścić przed funkcjami pomocniczymi
function doSomeStuff() { runHelper(); // other code...}
function runHelper() { // code...}
var● Ze względu na hoisting wszystkie deklaracje powinny znajdować się na początku
funkcji
● Możliwa ponowna deklaracja zmiennej
function doSomeStuff() { // all declarations with optional assigments: var a; var b = 'something'; var c = 'something';
// rest of the code...}
Block scope
● Sposób określania zasięgu zmiennych dostępny od ES6
● Najmniejszą jednostką scope'a jest blok kodu reprezentowany przez nawiasy klamrowe
● Mechanizm znany z wiekszości języków programowania
● Do deklaracji zmiennej wewnątrz block scope'a służą słowa kluczowe let oraz const
let & const
● Brak możliwości ponownej deklaracji zmiennej
● Dodatkowo const nie pozwala na ponowne przypisanie wartości do zmiennej (nie mylić z modyfikacją typów złożonych)
● Deklaracje zmiennych powinny znajdować się jak najbliżej ich wykorzystania
Kiedy var, let a kiedy const?
● Zasada: zawsze preferuj niemutowalny kod
● Dlaczego? Kod jest łatwiejszy w zrozumieniu i debugowaniu
● W ES6+ var nie powinno być nigdy stosowane
● const powinien stanowić domyślny wybór
● let powinien być stosowany tylko wtedy, gdy wartość zmiennej jest ponownie przypisywana (np. licznik pętli)
Zalety block scope
● Brak hoistingu - mniejsza podatność na błędy
function changeStyle() { const header = document.getElementById('header'); const article = document.getElementById('article'); const footer = document.getElementById('footer'); const mainColor = '#FF0000'; const accentColor = '#928c00';
header.style.backgroundColor = mainColor; header.style.color = accentColor; footer.style.backgroundColor = accentColor;}
Zalety block scope
● Lepsza organzacja kodu - deklaracje zmiennych w miejscu ich użycia
function changeStyle() { const mainColor = '#FF0000';
const header = document.getElementById('header'); const accentColor = '#928c00';
header.style.backgroundColor = mainColor; header.style.color = accentColor;
const footer = document.getElementById('footer');
footer.style.backgroundColor = mainColor;}
Zalety block scope
● Ograniczenie zasięgu zmiennych do wymaganego minimum
function changeStyle() { const mainColor = '#FF0000'; { const header = document.getElementById('header'); const accentColor = '#928c00'; header.style.backgroundColor = mainColor; header.style.color = accentColor; }
{ const footer = document.getElementById('footer'); footer.style.backgroundColor = mainColor; }}
Symulowanie bock scope'a w ES5
● Do symulowania block scope'a służy IIFE ( immediately-invoked function expression )
function changeRestStyle() { var mainColor = '#FF0000';
(function () { var header = document.getElementById('header'); var accentColor = '#928c00'; header.style.backgroundColor = mainColor; header.style.color = accentColor; }());
(function () { var footer = document.getElementById('footer'); footer.style.backgroundColor = mainColor; })();}
First-class functions
● Funkcje w JavaScript są obiektami pierwszej kategorii - mogą być traktowane jak wartości i przypisywane do zmiennych oraz stanowić elementy tablic i pola obiektów,
● Funkcje mogą być przekazywane jako argumenty do innych funkcji, mogą także stanowić wartość zwracaną z funkcji
● Funkcje, które przyjmuja inną funkcje jako argument lub zwracają inną funkcję są nazywane funkcjami wyższego rzedu (higher order functions)
● Funkcje mogą byc dowolnie zagnieżdżane, każda funkcja tworzy nowy scope
Czym jest domknięcie (closure)?
● Domknięcie to zapis w pamięci silnika, przechowujący funkcję wraz z jej środowiskiem
● Środowisko funkcji stanowią wszystkie zmienne zadeklarowane w scope otaczającym funkcję, które zostały wewnątrz niej użyte
● Funkcja ma dostęp do swojego lexical scope’a nawet jeśli jest wywoływana poza swoim scopem
● Domknięcie jest tworzone w miejscu deklaracji funkcji
Podstawowy przykład domknięcia
const name = 'Maciek';
function greet() { return `Hello, my name is ${name}.`;}
const result = greet(); // => "Hello, my name is Maciek."
Prosta funkcja wyższego rzędu
function createGreet() { const name = 'Maciek';
return function() { return `Hello, my name is ${name}.`; };}
const greet = createGreet();const result = greet(); // => "Hello, my name is Maciek."
Factory function
function createPerson(name) { const species = 'Homo Sapiens';
return { showDescription() { return `Person is a ${species} named ${name}.`; }, };}
const john = createPerson('John');john.showDescription(); // => "Person is a Homo Sapiens named John."
Module pattern (ES5)var calculator = function () { function add(a, b) { return a + b; }
function square(a) { return a * a; }
function sumOfSquares(a, b) { return add(aquare(a), square(b)); }
return { sumOfSquares: sumOfSquares, };}();
Partial applicationfunction partial(fn, ...parts) { return function (...rest) { return fn.apply(null, [...parts, ...rest]); };}
function add(...args) { return args.reduce(function (prev, next) { return prev + next; });};
const addFive = partial(add, 5);const addEleven = partial(add, 3, 8);
const result1 = addFive(7); // => 12const result2 = addEleven(3, 6); // => 20
Partial application (arrow functions)
const partial = (fn, ...parts) => (...rest) => fn.apply(null, [...parts, ...rest]);
const add = (...args) => args.reduce((prev, next) => prev + next);
const addFive = partial(add, 5);const addEleven = partial(add, 3, 8);
const result1 = addFive(7); // => 12const result2 = addEleven(3, 6); // => 20
Podsumowanie
● Twórz prosty, modularny, wolny od “magii” kod
● Ograniczaj ruchome części w kodzie, używaj const gdzie to tylko możliwe
● Ograniczaj zakres widoczności zmiennych, organizuj kod w precyzyjne scope'y
● Ukrywaj detale implementacyjne - jedną z metod jest użycie domknięć
● Twórz łatwy w ponownym wykorzystaniu, deklaratywny kod - wykorzystuj zalety funkcji wyższego rzędu i domknięć
Top Related