Przejdź do treści

Co const chroni w JavaScript

Często mówi się, że const oznacza „niezmienne". Realnie chroni tylko przypisanie nazwy, nie samą wartość - dlatego mutacja tablicy z const działa, a próba podstawienia jej czegoś nowego rzuca błąd.

Często słyszane skojarzenie: const znaczy „niezmienne”. W praktyce słowo kluczowe robi coś innego, a różnica wychodzi już przy pierwszej tablicy:

const teachers = ["Jan", "Nowak"];
teachers[1] = "Adam"; // OK - mutacja zawartości
console.log(teachers); // ["Jan", "Adam"]

teachers = ["Inni"]; // TypeError - re-binding

Mutacja działa, re-binding nie. To nie bug ani edge case - tak const jest zaprojektowane.

#Binding vs wartość

const mówi: „ta nazwa będzie wskazywać na tę samą rzecz przez cały blok”. To powiązanie identyfikatora z wartością nazywa się bindingiem - i właśnie binding const blokuje przed zmianą.

Nie blokuje natomiast zmiany zawartości tej rzeczy. Tablica, obiekt, instancja klasy - cokolwiek mutowalnego - można zmieniać dowolnie, byle identyfikator nadal wskazywał na ten sam obiekt w pamięci.

Dla typów prymitywnych (number, string, boolean, null, undefined, symbol, bigint) ta różnica zanika. Liczby albo stringa nie zmodyfikujesz - jedyne, co możesz zrobić, to przypisać do nazwy nową wartość. A na to const nie pozwala.

#Po co tak to zaprojektowano

Dwa powody z czasów ES2015:

  1. Tania kontrola. Sprawdzenie „czy nazwa wskazuje na tę samą rzecz” jest tanie i statycznie weryfikowalne. Sprawdzenie „czy zawartość się nie zmieniła” wymagałoby Object.freeze albo deep comparison na każdym kroku - to inny rząd kosztów.
  2. Zgodność z najczęstszą intencją. const typowo wyłapuje przypadkowe re-przypisania, a nie przypadkowe mutacje - ktoś nadpisuje twoją zmienną w pętli albo gałęzi if-a. Rozwiązuje to od razu, mutacji nie próbując regulować.

Jeśli logika wymaga prawdziwej immutability, są na to osobne narzędzia: Object.freeze (zamraża bezpośrednie pola obiektu - zagnieżdżone obiekty można dalej zmieniać), narzędzia do immutability (Immer, Immutable.js), readonly w TypeScript. Każde z nich pilnuje czego innego niż const.

#Słowo na koniec

const nie obiecuje, że wartość się nie zmieni. Obiecuje, że nazwa nadal wskazuje na to samo. To dwie różne rzeczy, i przy pierwszej tablicy z const zaczynają mieć znaczenie. Trzymaj const jako wybór domyślny - chroni bindingi taniej i jaśniej niż let - ale jeśli logika wymaga immutability, sięgnij po narzędzie, które to obiecuje.

(O wyborze między let a var - czyli o scope tej nazwy, nie o samym bindingu - piszę osobno w nocie o let i var.)

← Wróć do notatek