ECMAScript 2015 (ES6): nowości w JavaScripcie z przykładami

Mateusz Bogolubow

05-11-2026

Edycja, która zmieniła JavaScript: let i const, funkcje strzałkowe, destrukturyzacja, klasy, moduły, obietnice, Map i Set, generatory i Proxy. Każda nowość z przykładem.

ECMAScript 2015 (ES6): nowości w JavaScripcie z przykładami
ECMAScript 2015 (ES2015, wcześniej nazywany ES6) to 6. edycja standardu JavaScript, wydana w czerwcu 2015 roku, po prawie sześciu latach od ES5. Wprowadziła let i const, funkcje strzałkowe, klasy, moduły, obietnice (Promise), destrukturyzację, Map i Set, generatory i Proxy. Od tej edycji standard wydaje się co roku, a przykłady z tego artykułu działają w Node.js 22 i w Chromium 156.

ES2015 to edycja, po której JavaScript zaczął wyglądać inaczej: dostał klasy, moduły, obietnice i nową składnię funkcji. Poprzednia duża zmiana, ES5, wyszła w grudniu 2009 roku, więc między nimi minęło prawie sześć lat. W tym artykule przejdziemy po najważniejszych nowościach ES2015, każdą na krótkim przykładzie, a tam, gdzie w serwisie jest artykuł o powiązanym temacie, dostaniesz do niego link. Cały kod uruchomiłem w Chromium 156 i w Node.js 22, a wyniki w komentarzach pochodzą z tych uruchomień. Skąd w ogóle wziął się standard i dlaczego edycja 4 nigdy nie powstała, opisaliśmy w artykule historia JavaScriptu i czym jest ECMAScript.

Czym jest ES2015 i dlaczego nazywa się też ES6?

ECMAScript to oficjalna specyfikacja, na podstawie której działa JavaScript. ES2015 to 6. edycja standardu ECMA-262, wydana w czerwcu 2015 roku. Najpierw nazywano ją ES6, potem ES2015. To ta sama edycja pod dwiema nazwami, a od niej wszystkie kolejne oznacza się rokiem wydania.

Skala zmian była wyjątkowa, bo trafił do niej długo przygotowywany pakiet. Wśród pomysłów z porzuconej edycji 4 były m.in. klasy, moduły i generatory, a do standardu weszły one dopiero tutaj.

NowośćDo czego służy
let i constzmienne o zasięgu bloku zamiast var
Funkcje strzałkowekrótki zapis funkcji, this z otoczenia
Szablony napisównapisy z wyrażeniami i wieloma liniami
Destrukturyzacja, wartości domyślne, ...wyciąganie danych i elastyczne argumenty
Skrócony zapis obiektów{ imie }, klucze obliczane, metody bez function
Klasyczytelna składnia dla dziedziczenia
Moduły (import, export)podział kodu na pliki
Obietnice (Promise)praca z kodem asynchronicznym
Map, Setkolekcje z dowolnymi kluczami i bez duplikatów
Iteratory, generatory, for...ofwłasne sekwencje i pętle
Symbol, Proxyunikalne klucze i przechwytywanie operacji
Nowe metody wbudowaneArray.from, find, startsWith i inne

Czy mogę używać nowości z ES2015 już dziś?

Tak. To podstawa nowoczesnego JavaScriptu, dostępna w każdej współczesnej przeglądarce i w Node.js. Sprawdziłem dwa środowiska: Chromium 156 i Node.js 22.22.3. Wszystkie przykłady z tego artykułu dały w obu identyczne wyniki. Firefoksa i Safari nie sprawdzałem, więc zajrzyj do aktualnych tabel zgodności (np. MDN), jeśli piszesz kod dla starszych przeglądarek. Jeśli taki kod trzeba jednak wspierać, zamienia się go na starszą składnię narzędziami do budowania, o czym piszemy w artykule o bundlerach.

Czym let i const różnią się od var?

Zmienna zadeklarowana przez var ma zasięg całej funkcji, a nie bloku. To powodowało błędy, bo zmienna „wyciekała” z if albo pętli. let i const mają zasięg bloku, a odwołanie do nich przed deklaracją kończy się błędem.

function stary() {
  if (true) { var x = 1; }
  return x;
}
console.log(stary()); // 1 (var wycieka z bloku if)

try {
  (() => { if (true) { let y = 1; } return y; })();
} catch (e) {
  console.log(e.name); // "ReferenceError" (let istnieje tylko w bloku)
}

const o = { a: 1 };
o.a = 2; // wolno: const blokuje przypisanie, nie zmianę zawartości
console.log(o); // { a: 2 }

try {
  eval("const k = 1; k = 2;");
} catch (e) {
  console.log(e.name); // "TypeError"
}

Praktyczna zasada: domyślnie używaj const, a let tylko tam, gdzie wartość trzeba przepisać. var nie jest już potrzebny. Jeśli zastanawiasz się, jak działa this w funkcjach i metodach, zobacz artykuł dlaczego this nie działa.

Jak działają funkcje strzałkowe i czym różnią się od zwykłych?

Funkcja strzałkowa ma krótszy zapis, ale najważniejsza różnica dotyczy this: nie tworzy własnego, tylko używa tego z otaczającego kodu. Dzięki temu callback wewnątrz metody obiektu widzi ten sam obiekt.

const licznik = {
  wartosc: 0,
  zwiekszArrow() {
    [1, 2, 3].forEach(() => { this.wartosc++; });
    return this.wartosc;
  },
  zwiekszFunction() {
    "use strict";
    try {
      [1, 2, 3].forEach(function () { this.wartosc++; });
    } catch (e) {
      return e.name;
    }
  },
};

console.log(licznik.zwiekszArrow()); // 3
console.log(licznik.zwiekszFunction()); // "TypeError" (this jest tu undefined)

Funkcje strzałkowe wygodnie łączą się z metodami tablic, ale nie nadają się wszędzie, np. jako konstruktory. Pełne omówienie składni i typowych błędów znajdziesz w artykule o funkcjach strzałkowych, a podstawy zwykłych funkcji w tekście o deklaracji i wywołaniu funkcji.

Jak łączyć napisy z wartościami (szablony napisów)?

Szablon napisu (template literal) zapisujesz w odwrotnych apostrofach. Wewnątrz ${...} możesz wstawić dowolne wyrażenie, a sam napis może mieć wiele linii.

const imie = "Ala";
const n = 3;

console.log(`Cześć ${imie}, masz ${n * 2} wiadomości`);
// "Cześć Ala, masz 6 wiadomości"

const tekst = `linia 1
linia 2`;
console.log(tekst.split("\n").length); // 2

Jak wyciągać dane z obiektów i tablic (destrukturyzacja, wartości domyślne, rest i spread)?

Destrukturyzacja pozwala rozłożyć obiekt albo tablicę na zmienne jedną instrukcją. Do tego dochodzą wartości domyślne parametrów, parametr reszty (...reszta) i operator rozproszenia (...tablica).

const user = {
  name: "Ala",
  address: { city: "Łódź" },
  roles: ["admin", "dev", "ops"],
};

const { name, address: { city }, roles: [first, ...others] } = user;
console.log(name, city, first, others); // Ala Łódź admin ["dev", "ops"]

function powitaj(imie = "gościu", ...reszta) {
  return `${imie}:${reszta.length}`;
}
console.log(powitaj()); // "gościu:0"
console.log(powitaj("Ola", 1, 2, 3)); // "Ola:3"

const a = [1, 2];
const b = [...a, 3, 4];
console.log(b, Math.max(...b)); // [1, 2, 3, 4] 4

let p = 1, q = 2;
[p, q] = [q, p]; // zamiana wartości bez zmiennej pomocniczej
console.log(p, q); // 2 1

Dokładniej o każdym z tych mechanizmów piszemy w artykułach o destrukturyzacji i o tym, jak nie mylić rozproszenia z parametrem reszty. Zapis ... oznacza raz rozproszenie, raz resztę, zależnie od miejsca. To jedno z najczęściej mylonych miejsc ES2015.

Jak krócej zapisywać obiekty?

ES2015 pozwala pominąć powtórzenia w literale obiektu: skrócony zapis właściwości ({ imie } zamiast { imie: imie }), metody bez słowa function i klucze obliczane w nawiasach kwadratowych.

const klucz = "rola";
const imie = "Ala";

const u = {
  imie, // skrócony zapis
  [klucz]: "admin", // klucz obliczany
  powitaj() { // metoda bez "function"
    return "hej " + this.imie;
  },
};

console.log(u); // { imie: "Ala", rola: "admin", powitaj: [Function: powitaj] }
console.log(u.powitaj()); // "hej Ala"

Podstawy obiektów powtórzysz w artykule obiekty w JavaScript.

Jak działają klasy w JavaScripcie?

Słowo class daje czytelniejszy zapis tego, co wcześniej robiło się funkcjami konstruującymi i prototypami. To nadal te same prototypy pod spodem, tylko opakowane w wygodniejszą składnię: constructor, extends, super i metody statyczne.

class Zwierze {
  constructor(n) { this.n = n; }
  glos() { return "..."; }
  static typ() { return "zwierze"; }
}

class Pies extends Zwierze {
  glos() { return super.glos() + " hau"; }
}

const p = new Pies("Burek");
console.log(p.glos()); // "... hau"
console.log(Pies.typ()); // "zwierze" (metody statyczne się dziedziczą)
console.log(typeof Pies); // "function" (klasa to funkcja)
console.log(p instanceof Zwierze); // true

try {
  Pies(); // wywołanie bez new
} catch (e) {
  console.log(e.name); // "TypeError"
}

Dziedziczenie, hermetyzację i inne filary opisujemy w artykule o programowaniu obiektowym w JavaScript.

Jak podzielić kod na pliki (moduły import i export)?

Przed ES2015 JavaScript nie miał wbudowanego sposobu na podział kodu na pliki, więc powstawały obejścia. Moduły rozwiązują to składnią export i import. Plik modułu ma rozszerzenie .mjs (albo type: "module" w package.json), a w przeglądarce ładujesz go przez <script type="module">.

// matematyka.mjs
export const PI2 = Math.PI * 2;
export function dodaj(a, b) { return a + b; }
export default class Kalkulator { podwoj(x) { return x * 2; } }

// main.mjs
import Kalkulator, { dodaj, PI2 as obwod } from "./matematyka.mjs";
import * as mat from "./matematyka.mjs";

console.log(dodaj(2, 3)); // 5
console.log(new Kalkulator().podwoj(4)); // 8
console.log(obwod.toFixed(2)); // "6.28"
console.log(Object.keys(mat)); // ["PI2", "default", "dodaj"]

W dużych projektach moduły łączy się w paczki, co opisujemy w artykule o bundlerach. Ten przykład uruchomiłem w Node.js na dwóch plikach; w Chromium sprawdziłem te same wartości przez dynamiczny import().

Jak pracować z kodem asynchronicznym (Promise)?

Obietnica (Promise) reprezentuje wartość, która pojawi się w przyszłości. Zastąpiła zagnieżdżone callbacki łańcuchem .then i .catch, a metody Promise.all i Promise.race pozwalają łączyć wiele operacji.

const czekaj = (ms, wartosc) =>
  new Promise((resolve) => setTimeout(() => resolve(wartosc), ms));

const wyniki = await Promise.all([czekaj(20, "a"), czekaj(5, "b")]);
console.log(wyniki); // ["a", "b"] (kolejność jak w tablicy wejściowej)

const blad = await new Promise((_, reject) => reject(new Error("x")))
  .catch((e) => "złapano " + e.message);
console.log(blad); // "złapano x"

const szybszy = await Promise.race([czekaj(30, "wolny"), czekaj(5, "szybki")]);
console.log(szybszy); // "szybki"

Zapis await pochodzi z późniejszej edycji (ES2017), a użycie go poza funkcją, jak w przykładzie powyżej, działa w module (ES2022) i w konsoli przeglądarki. W zwykłym skrypcie owiń ten kod w funkcję async. W nowoczesnym kodzie Promise używa się zwykle razem z await. Jeśli chcesz zrozumieć, skąd wzięły się obietnice, zacznij od artykułu o callbackach, a kod asynchroniczny testuje się tak, jak pokazujemy w tekście o testowaniu kodu asynchronicznego. Praktyczny przykład warunkowego wykonania znajdziesz też w artykule jak uzależnić wykonanie kodu asynchronicznego od statusu odpowiedzi.

Do czego służą Map i Set?

Obiekty mają klucze tekstowe, a to bywa ograniczeniem. Map przyjmuje klucze dowolnego typu (także obiekty) i pamięta ich kolejność. Set to zbiór bez duplikatów, wygodny do usuwania powtórzeń z tablicy. Do tego dochodzą WeakMap i WeakSet, które nie blokują zbierania nieużywanych obiektów przez garbage collector.

const m = new Map();
const k = { id: 1 };
m.set(k, "obiekt").set(1, "liczba").set("1", "napis");

console.log(m.get(k), m.get(1), m.get("1"), m.size); // obiekt liczba napis 3

const unikalne = [...new Set([1, 2, 2, 3, 3, 3])];
console.log(unikalne); // [1, 2, 3]

const o = {};
o[k] = 1; // w zwykłym obiekcie klucz zamienia się na napis
console.log(Object.keys(o)); // ["[object Object]"]

Zwróć uwagę na ostatnie dwie linie: w zwykłym obiekcie kluczem staje się tekst "[object Object]", a nie sam obiekt, więc dwa różne obiekty nadpisałyby ten sam klucz. W Map każdy obiekt jest osobnym kluczem. Podobne zadania na tablicach opisaliśmy w artykułach tablice w JavaScript i metody tablicowe.

Czym są iteratory, generatory i pętla for...of?

Pętla for...of przechodzi po dowolnym obiekcie, który potrafi się iterować (tablica, napis, Map, Set). Iterator to obiekt z metodą next(), a generator (function* z yield) to prosty sposób na jego utworzenie.

function* liczby(do_) {
  for (let i = 1; i <= do_; i++) yield i;
}
console.log([...liczby(4)]); // [1, 2, 3, 4]

const g = liczby(2);
console.log(g.next()); // { value: 1, done: false }
console.log(g.next()); // { value: 2, done: false }
console.log(g.next()); // { value: undefined, done: true }

const zakres = {
  od: 1,
  do: 3,
  [Symbol.iterator]() {
    let i = this.od;
    const koniec = this.do;
    return {
      next: () => (i <= koniec
        ? { value: i++, done: false }
        : { value: undefined, done: true }),
    };
  },
};

const wartosci = [];
for (const x of zakres) wartosci.push(x);
console.log(wartosci); // [1, 2, 3]

const znaki = [];
for (const ch of "a😀b") znaki.push(ch);
console.log(znaki, "a😀b".length); // ["a", "😀", "b"] 4

Ostatni fragment pokazuje przy okazji ważną rzecz: for...of po napisie traktuje emoji jako jeden znak, a length liczy jednostki UTF-16 i daje 4. Różnicę między for...of a for...in wyjaśniamy w artykule pętla for of i pętla for in.

Do czego służy Symbol?

Symbol to nowy, unikalny typ danych. Każdy symbol jest inny, nawet jeśli ma ten sam opis, więc nadaje się na klucze, które nie kolidują z innymi. Najczęściej spotkasz go w wbudowanych kluczach, takich jak Symbol.iterator z przykładu wyżej.

const s1 = Symbol("id");
const s2 = Symbol("id");

console.log(s1 === s2); // false (każdy symbol jest unikalny)
console.log(s1.toString()); // "Symbol(id)"
console.log(typeof s1); // "symbol"

Jak przechwytywać operacje na obiekcie (Proxy)?

Proxy otacza obiekt i pozwala przechwycić odczyt, zapis i inne operacje. Reflect ma metody odpowiadające tym operacjom, więc w pułapce łatwo przekazać działanie do oryginału.

const cel = { a: 1 };
const odczyty = [];

const p = new Proxy(cel, {
  get(t, k, r) {
    odczyty.push(String(k));
    return Reflect.get(t, k, r);
  },
  set(t, k, v) {
    if (typeof v !== "number") throw new TypeError("tylko liczby");
    return Reflect.set(t, k, v);
  },
});

p.a;
try {
  p.b = "x";
} catch (e) {
  console.log(e.message); // "tylko liczby"
}
p.c = 5;

console.log(odczyty); // ["a"]
console.log(cel); // { a: 1, c: 5 }

Proxy jest narzędziem dla bardziej zaawansowanych zastosowań, takich jak walidacja i reaktywność. Jego użycie jako wzorca projektowego pokazujemy w artykule wzorzec projektowy Proxy.

Jakie nowe metody wbudowane dodała ES2015?

Poza dużymi mechanizmami ES2015 dołożyła mnóstwo drobnych metod, które na co dzień oszczędzają kod.

console.log(Array.from("abc")); // ["a", "b", "c"]
console.log(Array.from({ length: 3 }, (_, i) => i * 2)); // [0, 2, 4]
console.log([5, 12, 8].find((x) => x > 6)); // 12
console.log([5, 12, 8].findIndex((x) => x > 6)); // 1

console.log("devmentor".startsWith("dev")); // true
console.log("devmentor".endsWith("tor")); // true
console.log("devmentor".includes("ment")); // true
console.log("ab".repeat(3)); // "ababab"

console.log(Number.isNaN("x"), isNaN("x")); // false true
console.log(Math.trunc(-4.7), Math.sign(-3)); // -4 -1
console.log(Math.abs(0.3 - (0.1 + 0.2)) < Number.EPSILON); // true

Number.isNaN jest bezpieczniejsze niż globalne isNaN, bo nie zamienia wartości na liczbę przed sprawdzeniem. Ostatnia linia pokazuje poprawne porównywanie liczb zmiennoprzecinkowych: 0.1 + 0.2 === 0.3 daje false, więc porównuje się różnicę z Number.EPSILON.

Co dalej?

ES2015 to dopiero początek rocznego cyklu. Nowości z kolejnych edycji opisujemy systematycznie w osobnych artykułach, jedna edycja po drugiej: najnowsze to ES2024, ES2025 i ES2026, a kolejne będą dochodzić. Historię języka i pełną oś czasu edycji znajdziesz w artykule historia JavaScriptu i czym jest ECMAScript.

Jeśli chcesz uporządkować nowoczesny JavaScript pod okiem mentora, zobacz nasz kurs Zaawansowany JS.

Konsultacja i warsztaty devmentor.pl

Masz pytania?

Partnerzy

Firmy, z którymi współpracujemy

To partnerzy odpowiedzialni za staże po naszych kursach. Organizują je dla najlepszych absolwentów.

Zobacz wszystkich partnerów →
  • VR Medical
  • Sophona
  • Robot Force