Kiedy czytasz o JavaScripcie, możesz mieć pewne problemy z terminologią. ES5, ES6, ES7, ES8, ES2015, ES2016, ES2017, ECMAScript 2015, ECMAScript 2016, ECMAScript 2017 i tak dalej. Dowiedzmy się, co te terminy tak naprawdę znaczą, czym się różnią i dlaczego powstały.

Czym jest ECMAScript

Wszystkie powyższe terminy czy skróty nawiązują do standardu nazywanego ECMAScript. ECMAScript to standard języka programowania, a JavaScript jest implementacją. W wielkim uproszczeniu standard to prosty plan samochodu i tego, jak się zachowuje (ma koła, karoserię, kierownice, można nim jeździć, zatrzymać się, skręcać itp.), a konkretny samochód (np. Multipla… albo cokolwiek innego) to implementacja tego protokołu.

Poza JavaScriptem, który jest najpopularniejszy, są inne języki, które implementują ECMAScript, na przykład:

  • ActionScript – język skryptowy używany przez Flash
  • JScript – język skryptowy Microsoftu

Podczas tworzenia kolejnych wersji standardu ES, ich nazwy dostawały kolejne numerki lub rok powstania danej wersji, dlatego można spotkać na przykład ES6, ECMAScript 2015 oraz ES2015. Poniżej tabelka wyjaśniająca zawiłości tych wersji:

Wersja
Oficjalna nazwa
Długa nazwa
Data wydania
ES9ES2018ECMAScript 2018Czerwiec 2018
ES8ES2017ECMAScript 2017Czerwiec 2017
ES7ES2016ECMAScript 2016Czerwiec 2016
ES6ES2015ECMAScript 2015Czerwiec 2015
ES5.1ES5.1ECMAScript 5.1Czerwiec 2011
ES5ES5ECMAScript 5Grudzień 2009
ES4ES4ECMAScript 4Porzucone
ES3ES3ECMAScript 3Grudzień 1998
ES2ES2ECMAScript 2Czerwiec 1998
ES1ES1ECMAScript 1Czerwiec 1997

Ta tabelka powinna wam coś rozjaśnić. Jak widzicie, od 2009 roku coraz szybciej standard był rozwijany, a od 2015 roku już co roku były wydawane nowe wersje. Ciekawe co nas czeka w roku 2019… 🙂

Poniżej znajdziecie pierwszą część opisu wszystkich nowości dodanych w ES2015/ECMAScript 2015/ES6. Zapraszam.

let oraz const

Przed ES6 var był jedyną opcją na zadeklarowanie zmiennej. var jest function-scoped, czyli jest widoczna w funkcji lub zasięgu globalnym, jeżeli jest deklarowana poza jakąkolwiek funkcją.

function test() {
   var zmienna = 0;
   console.log(zmienna); // 0
}
console.log(zmienna); // ReferenceError: zmienna is not defined

Gdy zapomniałeś użyć var, zmienna była przypisywana do zasięgu globalnego, co go zaśmiecało, ale też działało. var nie było block-scoped, przez co mogły pojawiać się różne problemy.

var zmienna = 0
if (true) {
  var zmienna = 1;
}
console.log(zmienna); // 1

ES6 wprowadził let i const. Są to słowa kluczowe, które pozwalają na deklarację zmiennych, podobnie jak var, ale względem var mają parę różnic. Deklaruje się je tak:

let zmienna1 = ‘wartość 1’;
const zmienna2 = 'wartość 2';

Zmienne let i constblock-scoped, czyli są widoczne tylko w bloku, w którym zostały zadeklarowane:

let zmienna = 0;
if (true) {
  let zmienna = 1;
  const stala = 0;
}
console.log(zmienna); // 0
console.log(stala); // Uncaught ReferenceError: stala is not defined

Co więcej, w wielkim uproszczeniu let i const zachowują się na pierwszy rzut oka tak, jakby nie działał na nie hoisting:

console.log(a); // undefined
var a = 2;
console.log(b); // Uncaught ReferenceError: b is not defined
console.log(c); // Uncaught ReferenceError: c is not defined
let c = 2;

Prawda jest jednak inna. Sprawdźmy przykład:

var zmienna = 'wartosc zewnetrzna';
(function() {
  console.log(zmienna); // wartosc zewnetrzna
}());

let zmienna2 = 'wartosc zewnetrzna';
(function() {
  console.log(zmienna2); // wartosc zewnetrzna
}());

W obu przypadkach (dla var i let) kod zadziała tak samo. W zasięgu wewnętrznym funkcji nie ma zmiennej zmienna lub zmienna2, więc JS bierze z zasięgu wyżej. Dlatego wypisuje się w konsoli „wartosc zewnetrzna”. Teraz dodajmy sobie jedną małą deklarację kolejnej zmiennej, tym razem w funkcji już po console.log:

var zmienna = 'wartosc zewnetrzna';
(function() {
  console.log(zmienna); // undefined
  var zmienna = 'wartosc wewnetrzna';
}());

let zmienna2 = 'wartosc zewnetrzna';
(function() {
  console.log(zmienna2); // Uncaught ReferenceError: zmienna2 is not defined
  let zmienna2 = 'wartosc wewnetrzna';
}());

Tutaj zaczynają się schody. W przypadku var jest to, czego się spodziewaliśmy. Deklaracja zmiennej wewnętrznej jest hoistowana na początek funkcji, dlatego mamy undefined. Jest ok. Za to przy let – tu sprawa się komplikuje. Gdyby hoisting nie działał, mielibyśmy wypisane „wartosc zewnetrzna”. Gdyby działał normalnie, mielibyśmy „undefined”. Co więc się dzieje?

let i const są hoistowane na początek funkcji, ale wtedy tez zaczyna działać mechanizm Temporal Dead Zone (TDZ). Mechanizm ten sprawia, że nie można użyć zmiennych let i const przed ich zadeklarowaniem. JS zwraca po prostu błąd, zamiast wartości undefined. Jest to bardzo ważne zagadnienie i lepiej opisze je w oddzielnym wpisie. Na razie należy pamiętać, że tak naprawdę zmienne let i const są hoistowane, mimo że działają tak, jakby nie były.

const dodatkowo nie pozwala na zadeklarowanie zmiennej bez jej inicjalizowania, zabrania też przypisywania wartości na nowo. Pozwala tylko zmieniać wartości tablic i obiektów:

const zmienna = 0;
zmienna = 1; // TypeError: Assignment to constant variable.
const zmienna2; // SyntaxError: Missing initializer in const declaration
const obiekt = {};
obiekt.zmienna = 1;
console.log(obiekt); // { zmienna: 1 }
obiekt = {} // TypeError: Assignment to constant variable.

Najlepiej używać let jak var, a const w przypadku, kiedy wiemy, że nie będziemy zmieniali danej zmiennej. let to taki nowy var. const to stała jak np. liczba Pi.

Arrow Functions

ES6 wprowadza nowy sposób tworzenia funkcji, tak zwane Arrow functions, fat arrow functions czy funkcje strzałkowe. Największym plusem tej funkcji jest uproszczenie składni. Podstawowa funkcja strzałkowa wygląda następująco:

var funkcjaZwykla = function() {
    // tutaj cos robimy
}

const funkcjaStrzalkowa = () => {
  // tutaj cos robimy
}

Jak widzisz, pomijamy słowo kluczowe function. Jeżeli funkcja ma jeden, tylko jeden i aż jeden parametr, można pominąć nawiasy:

const funkcjaStrzalkowa = zmienna => {
  console.log(zmienna);
}

Jeżeli funkcja ma tylko jedno wykonanie, można pominąć klamry i napisać wszystko w jednej linii:

const funkcjaStrzalkowa = (zmienna) => console.log(zmienna);

Łącząc dwie poprzednie właściwości, uzyskujemy:

const funkcjaStrzalkowa = zmienna => console.log(zmienna);

Funkcje strzałkowe pozwalają również na użycie domniemanego return, czyli zwracanie wartości bez słówka kluczowego return. Działa to tylko dla funkcji jednolinijkowych (czyli dla jednego wywołania, którym byłoby return):

const funkcjaStrzalkowa = zmienna => zmienna * 4;
console.log(funkcjaStrzalkowa(5)) // 20

Jeżeli chcemy w ten sposób zwrócić obiekt, pamiętajmy, żeby obiekt ten otoczyć nawiasami, aby klamry obiektu nie były brane za klamry funkcji:

const funkcjaStrzalkowa = zmienna => ({ a: zmienna, b: zmienna*2 })

Porównanie funkcji podwajającej przy użyciu nowego i starego zapisu:

//Zwykły JS, zwykła funkcja
var podwojenie = function(numer){
    return numer * 2;
}
console.log(podwojenie(2))
  
//to samo używając arrow function z ES6
const podwojenie = numer => numer * 2;
console.log(podwojenie(2))

Funkcje tworzone przy użyciu fat arrow nie tworzy ona swojego this, a bierze go z kontekstu wywołania. Zwykle jest to rodzic. Inaczej mówiąc, odpowiada zwykłej funkcji z dodatkiem .bind(this). Dzięki temu możemy robić np. coś takiego:

const programista = {
  technologia: 'JS',
  prezentacja: () => {
    return `Programuje w ${this.technologia}`
  }
}

Class / Klasy

Jedynym sposobem na dziedziczenie przed ES6 było dziedziczenie przez prototypy. Mimo że działało, miało sporo problemów i nie było podobne do tego, co znamy z innych języków obiektowych, takich jak Java czy C#, czyli do dziedziczenia przez klasy. Dopiero ES6 wprowadza klasy, co pozwala uprościć często zawiły kod. Upodabnia też składnię do tej znanej z języków obiektowych. Klasy tworzy się następująco:

class Pracownik {
    constructor(name) {
        this.name = name;
    }
    przywitanie() {
        return "Witaj, nazywam sie " + this.name;
    }
}

Klasa ma swoją nazwę, konstruktor i wewnętrzne funkcje. Instancję klasy tworzy się przez operator new i nazwę klasy oraz argumenty przekazywane do konstruktora:

const pracownik = new Pracownik(‘Marcin’);

Kiedy tworzymy instancję klasy, odpalany jest konstruktor. W naszym przypadku jest to przypisanie imienia ‚Marcin’ do zmiennej w obiekcie o nazwie ‚name’. Żeby wywołać funkcję z klasy, trzeba użyć instancji klasy i nazwy funkcji:

pracownik.przywitanie() // Witaj, nazywam sie Marcin

Dziedziczenie przy pomocy nowych klas jest bardzo proste. Używa się słówka kluczowego extend. Przykład:

class Pracownik {
    constructor(name) {
        this.name = name;
    }
    przywitanie() {
        return "Witaj, nazywam sie " + this.name + ". ";
    }
}
 
class Programista extends Pracownik {
    constructor(name) {
        super(name);
    }
    programuj() {
        return super.przywitanie() + "Programuje!";
    }
}

const programista = new Programista('Andrzej');
console.log(programista.programuj());

Jeżeli chcesz odwołać się do komponentu, z którego dziedziczysz, użyj ‚super’. Klasy ES6 pozwalają nam również tworzyć metody statyczne. Aby je zrobić, należy przed nazwą metody użyć słówka kluczowego ‚static’:

class Pracownik {
    constructor(name) {
        this.name = name;
    }
    static przywitanie() {
        return "Witaj”;
    }
}

Person.przywitanie();

Klasy ES6 niestety nie dają nam możliwości tworzenia metod prywatnych. Pozwalają za to na stworzenie getterów i setterów:

class Pracownik {
  constructor(name) {
    this._name = name
  }
  set name(value) {
    this._name = value
  }
  get name() {
    return this._name
  }
}

Default parameters / parametry domyślne

ES6 daje nam możliwość dodawania wartości domyślnych do parametrów funkcji. Przykład:

const funkcja = (zmienna = ‘text’) => {
  console.log(zmienna);
}

funkcja(‘zmienna’); // zmienna
funkcja(); // text

Powyższa funkcja wypisze w konsoli wartość przekazaną do niej lub wartość domyślną, w naszym wypadku ‚text’; Parametry domyślne działają również dla wielu parametrów w jednej funkcji:

const funkcja = (zmienna1 = ‘text1’, zmienna2 = ‘text2’, zmienna3 = ‘text3’) => {
  // ...
}

Jeżeli przekazujemy do funkcji obiekt jako parametr i chcemy, żeby obiekt ten miał wartość domyślną, używany poniższej składni:

const funkcja = ({imie: ‘Dawid’}) => {
  console.log(imie);
}

funkcja({imie: ‘Test’}); // Test
funkcja({imie: ‘Test’, nazwisko: ‘Test2’}); // Test
funkcja(); // Dawid

Podsumowanie

Historia JavaScriptu jest ciekawa i zawiła. Powyższe nowości są najpopularniejsze, ale to nie wszystkie zmiany, jakie się pojawiły. W kolejnych częściach poznasz pozostałe nowości, gdyż jest ich za dużo jak na jeden wpis.

Tymczasem, jeżeli masz jakieś pytania – zapraszam do komentowania 🙂



2 KOMENTARZE

    • Prawda, moja wina. Powiem szczerze, że nie znałem tego, ale jest to świetny temat za wpis. Dziękuję, że jak zwykle pięknie wyłapujesz takie smaczki 🙂 Naprawdę, TDZ to ciekawa sprawa.

ZOSTAW ODPOWIEDŹ

Please enter your comment!
Please enter your name here

This site uses Akismet to reduce spam. Learn how your comment data is processed.