JavaScript nabízí tři způsoby, jak deklarovat proměnné:
var, let a const. Pro mnoho
programátorů není úplně jasné, kdy kterou z nich použít, většina
tutoriálů a linterů vás nutí používat je špatně. Pojďme si ukázat,
jak psát čistší a srozumitelnější kód bez zbytečných pravidel, která
nám ve skutečnosti nepomáhají.
Začněme tím
nejnebezpečnějším
JavaScript má jednu zákeřnou vlastnost: pouhým opomenutím deklarace
proměnné můžete nevědomky používat globální proměnnou. Stačí
zapomenout na var, let nebo const:
function calculatePrice(amount) {
price = amount * 100; // Opomenutí! Chybí 'let'
return price; // Používáme globální proměnnou 'price'
}
function processOrder() {
price = 0; // Používáme tu samou globální proměnnou!
// ... nějaký kód volající calculatePrice()
return price; // Vracíme úplně jinou hodnotu, než čekáme
}
Tohle je noční můra každého vývojáře – kód funguje zdánlivě
správně, dokud nezačne někde jinde v aplikaci něco záhadně selhávat.
Debugování takových chyb může zabrat hodiny, protože globální proměnná
může být přepsána kdekoliv v aplikaci.
Proto je naprosto zásadní vždy deklarovat proměnné pomocí
let nebo const.
Zapomeňte na var
Klíčové slovo var je v JavaScriptu od jeho počátku v roce
1995 a nese s sebou pár problematických vlastností, které byly v době
vzniku jazyka považovány za features, ale časem se ukázaly jako zdroj mnoha
chyb. Po dvaceti letech vývoje jazyka se autoři JavaScriptu rozhodli tyto
problémy řešit – ne opravou var (kvůli zachování zpětné
kompatibility), ale představením nového klíčového slova let
v ES2015.
Na internetu najdete spoustu článků rozebírajících problémy
var do nejmenších detailů. Ale víte co? Není potřeba se
v tom babrat. Berme var prostě jako překonaný archaismus a
pojďme se soustředit na moderní JavaScript.
Kdy použít let
let je moderní způsob deklarace proměnných
v JavaScriptu.
Příjemné je, že proměnná existuje vždy pouze uvnitř bloku kódu (tedy
mezi složenými závorkami), kde byla definována. To dělá kód
předvídatelnější a bezpečnější.
if (someCondition) {
let temp = calculateSomething();
// temp je dostupná jen zde
}
// temp už zde neexistuje
V případě cyklů je deklarace přísně vzato umístěna před
složenými závorkami, ale nenechte si tím zmást, proměnná existuje jen
v cyklu:
for (let counter = 0; counter < 10; counter++) {
// Proměnná counter existuje jen v cyklu
}
// counter už zde nejsou dostupné
Kdy použít const
const slouží k deklarování konstant. Typicky jde
o důležité hodnoty na úrovni modulu nebo aplikace, které se nikdy
nemají měnit:
const PI = 3.14159;
const API_URL = 'https://api.example.com';
const MAX_RETRY_ATTEMPTS = 3;
Je ale důležité pochopit jeden klíčový detail: const pouze
zabraňuje přiřazení nové hodnoty do proměnné – neřeší, co se
děje s hodnotou samotnou. Tento rozdíl se projevuje zejména u objektů a
polí (pole je ostatně také objekt) – const z nich nedělá
immutable objekty, tj. nezabraňuje změnám uvnitř objektu:
const CONFIG = {
url: 'https://api.example.com',
timeout: 5000
};
CONFIG.url = 'https://api2.example.com'; // Toto funguje!
CONFIG = { url: 'https://api2.example.com' }; // Toto vyhodí TypeError!
Pokud potřebujete skutečně neměnný objekt, musíte jej nejprve
zmrazit.
Dilema let vs
const
Nyní se dostáváme k zajímavější otázce. Zatímco u var
vs let je situace jasná, použití const je
předmětem mnoha diskuzí v komunitě. Většina tutoriálů, style-guides a
linterů prosazuje pravidlo „používej const všude, kde
můžeš“. Takže použití const vídáme zcela běžně
v tělech funkcí nebo metod.
Pojďme si vysvětlit, proč je tato populární „best practice“ ve
skutečnosti anti-pattern, který dělá kód méně čitelný a zbytečně
svazující.
Přístup „pokud se proměnná v kódu nepřepisuje, měla by být
deklarována jako const“ se na první pohled jeví logický.
Proč by jinak bůh stvořil const? Čím víc „konstant“, tím
bezpečnější a předvídatelnější kód, že? A navíc rychlejší,
protože ho kompilátor může lépe optimalizovat.
Jenže celý tento přístup je ve skutečnosti nepochopení toho, k čemu
konstanty slouží. Jde především o komunikaci záměru – opravdu
chceme sdělit ostatním vývojářům, že do této proměnné se už nesmí
nic přiřadit, nebo do ní jen náhodou v současné implementaci nic
nepřiřazujeme?
// Skutečné konstanty - hodnoty, které jsou konstantní ze své podstaty
const PI = 3.14159;
const DAYS_IN_WEEK = 7;
const API_ENDPOINT = 'https://api.example.com';
// vs.
function processOrder(items) {
// Toto NEJSOU konstanty, jen náhodou je nepřepisujeme
const total = items.reduce((sum, item) => sum + item.price, 0);
const tax = total * 0.21;
const shipping = calculateShipping(total);
return { total, tax, shipping };
}
V prvním případě máme hodnoty, které jsou konstantami ze své
podstaty – vyjadřují neměnné vlastnosti našeho systému nebo důležitá
konfigurační data. Když někde v kódu vidíme PI nebo
API_ENDPOINT, okamžitě chápeme, proč jsou tyto hodnoty
konstanty.
V druhém případě používáme const jen proto, že zrovna
teď náhodou hodnoty nepřepisujeme. Ale není to jejich podstatná
vlastnost – jsou to běžné proměnné, které bychom v příští
verzi funkce klidně mohli chtít změnit. A když to budeme chtít udělat,
const nám v tom bude zbytečně bránit.
V dobách, kdy byl JavaScript jeden velký globální kód, mělo smysl
snažit se zabezpečit proměnné proti přepsání. Ale dnes píšeme kód
v modulech a třídách. Dnes je běžné a správné, že scope je malá
funkce a v jejím rámci vůbec nemá smysl rozdíl mezi let a
const řešit.
Protože to vytváří naprosto zbytečnou kognitivní zátěž:
- Programátor musí při psaní přemýšlet: „Budu tuhle hodnotu měnit?
Ne? Tak musím dát const…“
- Čtenáře to ruší! Vidí v kódu
const a ptá se: „Proč
je tohle konstanta? Je to nějaká důležitá hodnota? Má to nějaký
význam?“
- Za měsíc potřebujeme hodnotu změnit a musíme řešit: „Můžu změnit
const na let? Nespoléhá na to někdo?“
Používejte jednoduše let a tyto otázky nemusíte vůbec
neřešit.
Ještě horší je, když toto rozhodnutí dělá automaticky linter. Tedy
když linter „opraví“ proměnné na const, protože vidí jen jedno
přiřazení. Čtenář kódu pak zbytečně přemýšlí: „Proč tady musí
být tyto proměnné konstanty? Je to nějak důležité?“ A přitom to není
důležité – je to jen shoda okolností. Nepoužívejte v ESLint pravidlo
prefer-const!
Mimochodem, argument o optimalizaci je mýtus. Moderní JavaScript engine
(jako V8) dokáže snadno detekovat, zda je proměnná přepisována nebo ne,
bez ohledu na to, jestli byla deklarována pomocí let nebo
const. Takže používání const nepřináší
žádný výkonnostní benefit.
Implicitní konstanty
V JavaScriptu existuje několik konstrukcí, které implicitně vytvářejí
konstanty, aniž bychom museli použít klíčové slovo const:
// importované moduly
import { React } from 'react';
React = something; // TypeError: Assignment to constant variable
// funkce
function add(a, b) { return a + b; }
add = something; // TypeError: Assignment to constant variable
// třídy
class User {}
User = something; // TypeError: Assignment to constant variable
Je to logické – tyto konstrukce definují základní stavební bloky
našeho kódu a jejich přepsání by mohlo způsobit chaos v aplikaci. Proto
je JavaScript automaticky chrání proti přepsání, stejně jako kdyby byly
deklarovány pomocí const.
Konstanty ve třídách
Třídy byly do JavaScriptu přidány relativně nedávno (v ES2015) a
jejich funkcionalita teprve postupně dospívá. Například privátní členy
označené pomocí # přišly až v roce 2022. Na podporu
konstant ve třídách JavaScript stále čeká. Prozatím můžete používat
static, který ale není zdaleka to samé – označuje hodnotu
sdílenou mezi všemi instancemi třídy, nikoliv však neměnnou.
Závěr
var nepoužívejte – je to přežitek
const používejte pro skutečné konstanty na
úrovni modulu
- Ve funkcích a metodách používejte
let – je to
čitelnější a jasnější
- Nenechte linter automaticky měnit
let na
const – není to o počtu přiřazení, ale o záměru