ECMAScript 6+

omówienie najważniejszych elementów

Zmienne

Zasięgi zmiennych

let

let - zakres blokowy

let a = 2;
let b = 3;
{
  let b = 78;
  console.log(a, b); // 2 78
}
console.log(a, b); // 2 3

let - lepsze zarządzanie pamięcią

{
  let a = 2;
  var b = 3;
}

console.log(b); // 3

// error: a is not defined
console.log(a);

let - a może w pętlach?

for(let i = 0; i < 5; i++) {
   console.log(i); // 0 1 2 3 4
}
 
//error: i is not defined
console.log(i);

let - pętle i deklaracja funkcji

var tab = [];
for(var i = 0; i < 4; i++) {
  tab[i] = function() {
      console.log(i);
  }
}
tab[1]();       // 4 Dlaczego ?
console.log(i); // 4

Problem z var

let - pętle i deklaracja funkcji

let tab = [];
for(let i = 0; i < 4; i++) {
  tab[i] = function() {
      console.log(i);
  }
}
tab[1]();       // 1 Idealnie!!!

// Error: i is not defined
console.log(i);

Rozwiązanie

const

const - deklaracja stałych

{
  const  myName = "John";
  const  counter = 0;

  counter++;
}
//TypeError: Assignment to constant variable.

Stałe mają zakres blokowy!

const - modyfikacja zmiennej czy zmiana jej wartości?

{
  const counter = 1;
  const points = [200, 100];
  counter++; // błąd
  points = [300, 500]; // błąd
}

const - jeśli nie zmieniamy adresu zmiennej, to ok :)

{
  const person = {name: "Ala"};
  person.age = 21;
  console.log(person); //{name: "Ala", age: 21}
 
  const points = [100, 200, 300];
  points.push(122);
  console.log(points); //[100, 200, 300, 122]
}

hoisting - var - przypomnienie

console.log(points);  // undefined
var points = 5;

hoisting - let i const

{
  //ReferenceError: Cannot access 
  //'points' before initialization
  console.log(points); 
  let points = 5;
}

Temporal Dead Zone

Zasięg zmiennych

Rodzaje zakresów

Razem z ES6 dostaliśmy do dyspozycji możliwość deklaracji zmiennych w obrębie bloku kodu { }.

Wcześniej (w ES5) JavaScript tworzył zakres zmiennych tylko poprzez funkcje.

 

Rodzaje zakresów

Zmienne mogą być:

globalne – to zmienne nie zadeklarowane w żadnym zakresie lub zadeklarowane bez słowa kluczowego var.
Zmienne globalne są widoczne w całym naszym programie i są niszczone dopiero podczas zamknięcia okna przeglądarki.

lokalne – to zmienne zadeklarowane w zakresie funkcji przy pomocy słowa kluczowego var lub jeśli korzystamy z let lub const to w zakresie bloku {}
Zmienne lokalne są widoczne  w zakresie funkcji, w której zostały stworzone oraz w funkcjach wewnętrznych. Są niszczone w chwili, w której funkcja je tworząca się kończy, wyjątek Domknięcia.

 

Zakres globalny i lokalny

var a = "global_a";
b = "global_b";
function myScope(){
  var a = "local_a";
  console.log(a); // "local_a"
  console.log(b); // global_b
}
console.log(a); // "global_a"
console.log(b); // "global_b"

Zakres blokowy

{
   let a = 34;
   console.log(a);
}
console.log(x);//ReferenceError

Zakres zmiennej let oraz const jest blokowy to znaczy:

Rodzaje zakresów - problem

for (var i = 0; i < 5; i++) {
    setTimeout(function() {
        console.log(i);
    }, i*1000);
}
//5
//5
//5
//5
//5

Dlaczego?

Rodzaje zakresów - problem

for (var i = 0; i < 5; i++) {
    setTimeout(function() {
        console.log(i);
    }, i*1000);
}

Zgodnie ze sposobem działania zakresu, wszystkie wywołania funkcji, mimo iż są zdefiniowane oddzielnie w poszczególnych iteracjach pętli, to jednak są umieszczone w tym samym współdzielonym zakresie globalnym, który tak naprawdę zawiera tylko jedną wersję zmiennej "i".
Potrzebujemy zatem zakresu tzw. domknięcia dla każdej iteracji.

Rodzaje zakresów - problem

for (var i = 0; i < 5; i++) {
    (function(i) {
        setTimeout(function() {
            console.log(i);
        }, i*1000);
    })(i);
}

Użycie funkcji IIFE wewnątrz każdej iteracji spowodowało utworzenie nowego zakresu dla zmiennej. Należy oczywiście przekazać zmienną "i" do nowego zakresu:

//0
//1
//2
//3
//4

Rodzaje zakresów - problem

for (let i = 0; i < 5; i++) {
 setTimeout(function() {
  console.log(i);
 }, i*1000);
}

W poprzednim przykładzie do stworzenia zakresu stworzyliśmy funkcję, ale tak naprawdę potrzebujemy zakresu bloku. Zmienna "i" zostanie zadeklarowana dla każdej iteracji:

//0
//1
//2
//3
//4

Domknięcie

function testClousure(name) {
 setTimeout(function noName() {
  console.log("hello ", name);
 }, 1000);
}

testClousure("Agata");

W poprzednim przykładzie korzystaliśmy z IIFE, aby stworzyć nowy zakres (domknięcie) dla zmiennej "i". Czym było to domknięcie? Spójrz na poniższy przykład:

Domknięcie

function testClosure(name) {
 setTimeout(function noName() {
  console.log("hello ", name);
 }, 1000);
}

testClosure("Agata");

Funkcja noName() zostaje przekazana funkcji setTimeout.

Zakres funkcji noName() (Zakres domknięcia) jest stworzony za pomocą funkcji testClosure(), dzięki temu funkcja noName() może używać zmiennej name.

1s po wykonaniu funkcji testClosure() jej zakres wewnętrzny powinien zostać usunięty, ale funkcja noName() wciąż ma domknięcie poprzez ten zakres.

Metody tablicowe ES6

map 

map to metoda, która jest wywoływana na elementach tablicy.

 

Różnica pomiędzy forEach a map jest taka, że map zwraca nową tablicę.

const arr = [1,2,3];
const newArr = arr.map((el) => { 
	return el*2;
})
console.log(newArr); //1,4,6

map - parametry

callback - funkcja wykonywana dla każdego elementu tablicy przyjmuje parametry:

  • currentValue - bieżąca wartość - wymagany parametr

  • index - indeks bieżącego elementu tablicy - opcjonalny

  • array - odniesienie do obiektu, na którym wywołujemy funkcję - opcjonalny

  • thisArg - dodajemy w callbacku, ustawiana wartość zmiennej this - opcjonalny

arr.map(callback(currentValue[, index[, array]]) {
  // execute something
}[, thisArg]);

filter 

Metoda filter (jest bardzo podobna do map().

Różnicą jest to, że filter() zwraca nową tablicę z wartościami, które w wyniku działania warunku są prawdą, np. value >= 18 (chcemy zostawić wartości większe lub równe 18).

W przypadku tej metody, nie możemy poddawać modyfikacji tych elementów.

filter 

const people = [
  {name: "John", age: 23},
  {name: "Ann", age: 16}
];
 
const adults = 
  people.filter(function(person) {
    return person.age >= 18;
});
 
console.log(adults);
// [ { name: 'John', age: 23 } ]

reduce 

const array1 = [1, 2, 3];

const sum = 
  array1.reduce(function(total, item) {
    return total + item;
});

console.log(sum); // 6

Wywołaj funkcję dla każdego z elementów w taki sposób, żeby mieć dostęp do wartości otrzymanej w wyniku wykonania funkcji dla poprzedniego elementu (przydatne przy sumowaniu).

Inne nowe funkcje interacyjne

  • arr.some() – sprawdź, czy jakikolwiek element spełnia dany warunek

  • arr.every() – sprawdź, czy wszystkie elementy spełniają dany warunek

Arrow function

Arrow function

//just function
const test = function(x) { 
    return 2 * x;
}

//arrow function :)
const test = (x) => 2 * x;

arrow function - różne zapisy

//Bez parametrów - trzeba nawias
const test = () => 2;
//1 parametr - nie trzeba nawiasu
const test = x =>  x * 2;

//1 parametr + ciało funkcji
const test = x => { 
    let a = 4;
    return x * a; 
}

arrow function - różne zapisy

//Kilka parametrów - trzeba nawias
const sum = (x, y) => x + y; 
//Kolejna linijka
const sum = (x, y) => (
    x + y
)
//Immediately Invoked Function Expression
( (x) =>  x * 2 ) (5)

arrow function - map, filter

const points = [10, 25, 30];
const more = points.map(p => p * 2);
console.log(more); // [20, 50, 60]
const points = [10, 25, 30];
const moreThan20 = points.filter(p => p > 20);
console.log(moreThan20); // [25, 30]

Przykład this

function Counter() {
  this.number = 0;
}
const x = new Counter();

Mamy konstruktor Counter

Przykład this

function Counter() {
  this.number = 0;

  this.idInterval = setInterval(function inc() {
    this.number++;
    console.log(this.number);
  }, 1000);
}
const x = new Counter();

Zwiększamy licznik o 1 co sekundę.

Przykład this

function Counter() {
  this.number = 0;

  this.idInterval = setInterval(function inc() {
    console.log(this); //Window !!!
  }, 1000);
}
const x = new Counter();

this to Window (w przeglądarce)

arrow function - this

function Counter() {
  this.number = 0;

  this.idInterval = setInterval( () => {
    console.log(this); //Counter  - yeaahhh
  }, 1000);
}
const x = new Counter();

naprawiamy

arrow function - this

function Counter() {
  this.number = 0;

  this.idInterval = setInterval(() => {
    this.number++;
    console.log(this.number);
  }, 1000);
}
const x = new Counter();

naprawiamy

 12 3 4 5 6 7 8 9 10 11 12 13 14 15 16

arrow function - kiedy nie używać?

const person = {
  age : "Jan",
  getAge: () => {
    //this to Window (w przeglądarce)
    return this.age;
  }
};
console.log(person.getAge());

- w metodach obiektów

arrow function - kiedy nie używać?

const button = document.querySelector('#music');
button.addEventListener('click', () => {
  this.classList.toggle('on');
});

 - Callback functions z dynamicznym kontekstem

Arrow function

  • krótki zapis

  • zawsze anonimowe

  • nie mogą być konstruktorami

  • nie mają własności prototype

  • nie jest dla nich tworzone wyrażenie this

Zadania

Klasy

class Vehicle {
  constructor(type) {
    this.type = type;
  }
  getType() {
    return this.type;
  }
}
class Car extends Vehicle {
  constructor(type, age) {
    super(type);
    this.age = age;
  }
}
let bmw = new Car("BMW", 4);
console.log(bmw);
class Calculator{
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
  add() {
    return this.x * this.y;
  }
}

class MyCalculator extends Calculator{
  constructor(x, y, z) {
    super(x, y);
    this.z = z;
  }
  multiply() {
    return super.add() * this.z;
  }
}

Super

Rest / spread operator

and properties

Operator rozproszenia

(spread operator)

const tab = [2, 3, 4];
console.log(...tab);
// 2, 3, 4

const myName = "Agata";
console.log( [...myName] );
//["A", "g", "a", "t", "a"]

Operator rozproszenia

(spread operator)

const sum = (x, y, z) => {
  return x + y + z;
}
const result = sum(...[2, 3, 4]);
console.log(result); //9

Operator rozproszenia

(spread operator)

const tab1 = [8, 9];
const tab2 = [1, 2, ...tab1, 3];
console.log(tab2); // [1, 2, 8, 9, 3]

Operator reszty

(rest operator)

const test = (...points) => {
    console.log(points);
}

test(1,2,3);
//[1, 2, 3]

Operator reszty

(rest operator) - ograniczenie

const test = (...points, p) => {
    console.log(points);
}

//Error: Rest parameter must be last formal 
//parameter

Rest / Spread properties

const person = {
    firstName: 'Jan',
    lastName: 'Nowak',
    country: 'Poland',
    city: 'Warsaw',
};
const { firstName, lastName, ...rest } = person;
console.log(firstName); // Agata
console.log(lastName); // Malec
console.log(rest); // { country: 'Poland', city: 'Warsaw' }

Destrukturyzacja

Dane z obiektów i tablic możemy wyciągać poprzez tzw. przypisanie destrukturyzujące, czyli takie, które skraca zapis.

Destrukturyzacja

Destrukturyzacja tablic

const arr = ["Azor", "Mruczek", "Reksio"];
const [a, b] = arr;

console.log(a, b) //"Azor", "Mruczek"

Destrukturyzacja tablic

const arr = ["Azor", "Mruczek", "Reksio"];
const [a, ,c] = arr;

console.log(a, c) //"Azor", "Reksio"

Jeśli chcemy pominąć jakąś wartość, to używamy przecinka:

Destrukturyzacja tablic

const arr = ["Azor", "Mruczek"];
const [a="nie ma", b="nie ma", c="nie ma"] = arr;

console.log(a, b, c) //"Azor", "Mruczek" , "nie ma"

Możemy ustawić zmiennych wartości domyślne ( w przypadku braku takiej wartości w tablicy)

Destrukturyzacja tablic

const arr = ["Azor", "Mruczek", "Reksio"];
const [a, ...dogs] = arr;

console.log(a, dogs) //"Azor", ["Mruczek" , "Reksio"]

Możemy też skorzystać z operatora rest

Destrukturyzacja tablic

let a = 1;
let b = 2;

[a, b] = [b, a];

console.log(a); // 2
console.log(b); // 1

Zamiana wartości bez trzeciej zmiennej.

Destrukturyzacja obiektów

const dog = {
    name : "Reksio",
    owner : "Jan Kowalski",
    age : 2
}

const { name, owner, age } = dog;

Destrukturyzacja obiektów

const dog = {
    name : "Reksio",
    owner : "Jan Kowalski"
}

const { 
  name = "Azor", 
  owner = "nie ma", 
  age = "brak danych" 
} = dog;

Jeśli w obiekcie nie będzie jakiejś wartości zostanie zwrócony undefined. Możemy tego uniknąć również stosując wartości domyślne.

Destrukturyzacja obiektów

const dog = {
    name : "Reksio",
    owner : "Jan Kowalski"
}

const { 
  name: dogName = "Azor", 
  owner = "nie ma", 
  age = "brak danych" 
} = dog;

Jeśli chcemy stworzyć zmienne o innych nazwach niż klucze obiektu, używamy dwukropka.

Destrukturyzacja i funkcje

const getData = ({name, owner}) => {
  console.log(name, owner)
}

const dog = {
    name : "Reksio",
    owner : "Jan Kowalski"
}

getData(dog)
const getData = (obj) => {
  console.log(obj.name, obj.owner)
}

const dog = {
    name : "Reksio",
    owner : "Jan Kowalski"
}

getData(dog)

zamiast:

mogę tak: 

Template string

Template string

const city = `Warszawa.`;

const text = `Witaj ${city}`
console.log( text )

//"Witaj Warszawa"

Template string

const a = 2;
const b = 3;

console.log(`Po dodaniu ${a} i ${b} mamy ${a+b}`);

//Po dodaniu 2 i 3 mamy 5

Template string

console.log(`Ja uwielbiam ją
ona tu jest 
i tańczy dla mnie
o o o o 
`);

//Ja uwielbiam ją
//ona tu jest 
//i tańczy dla mnie
//o o o o

Promises &

Async / Await

Podstawy

Promises 

Przykład: dziadek obiecał nam Ferrari (złożył obietnicę), ale nie powiedział kiedy nam go kupi.

 

Promise może być w jednym z 3 stanów:

 

  • Oczekujący (pending) - jeszcze nie wiemy czy dziadek dotrzyma obietnicy czy nie.
  • Rozwiązany (resolved) - jeździmy super furą,
  • Odrzucony (rejected) - to  chyba jakiś żart , a nie obietnica! ;) 

Promises

const getCar = () => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('Ferrari!');
        }, 2000);
    });
}

const promisedCar = getCar(); // zwraca obiekt typu Promise
promisedCar
    .then(car => console.log('Kocham dziadka za ', car))
    .catch(error => console.log('Dziadek żartował', error));

Jeśli obietnica zostanie spełniona wykona się kod w funkcji then, w przeciwnym wypadku z funkcji catch. Niestety nie wiemy dokładnie kiedy to nastąpi, może nastąpić w ciągu 1 sekundy, a może nastąpić za miesiąc lub rok

Async / Await

Słowo async - będzie oznaczać funkcje jako asynchroniczną (czyli taką, która korzysta z Promises)

Słowo await - będzie sprawiało, że wywołania Promises będą sekwencyjne - synchroniczne

const getCar = () => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('Ferrari!');
        }, 2000);
    });
}

const waitForCar = async () => {
  const car = await getCar();
  console.log("Mam" , car);
}
waitForCar()

Async / Await - try .. catch

const getCar = () => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            reject('Nie ma auta');
        }, 2000);
    });
}

const waitForCar = async () => {
  try {
    const car = await getCar();
    console.log(car);
  } catch(error) {
  	console.log(error);
  }
}

waitForCar();

Fetch API

Fetch API

Fetch Api to narzędzie, dzięki któremu możemy komunikować się z różnymi źródłami danych, podobnie jak w przypadku XMLHttpRequest.  Różnica między nimi polega na tym, że Fetch API korzysta z tzw. Promises, dzięki temu unikamy kodu spaghetti.

Korzystanie z fetch API jest także dużo prostsze ( nie trzeba pamiętać o wielu rzeczach tak jak w przypadku XMLHttpRequest).

Fetch API

Fetch Api dostarcza nam odpowiedni interfejs do wykonywania zapytań i odbierania odpowiedzi.

Mamy tutaj do dyspozycji funkcję fetch(), dzięki, której w prosty sposób możemy wykonywać asynchroniczne zapytania.

fetch('https://pokeapi.co/api/v2/pokemon/pikachu')
 .then(response => response.json())
 .then(data => console.log(data));

Najprostsze użycie fetch'a. Przyjmuje on jeden argument - ścieżkę do zasobu, który chcemy pobrać. Zwraca on promise, który zawierający odpowiedź (obiekt Response).

Fetch API

fetch('https://pokeapi.co/api/v2/pokemon/pikachu')
 .then(response => response.json())
 .then(data => console.log(data));

obiekt Response (to odpowiedź HTTP, nie plik JSON, dlatego musimy go najpierw wydobyć z tego obiektu używając funkcji json()

Fetch API

fetch(url, {
 method: 'POST', // *GET, POST, PUT, DELETE, etc.
 mode: 'cors', // no-cors, *cors, same-origin
 cache: 'no-cache', // *default, no-cache, reload, force-cache, 
  //only-if-cached
 credentials: 'same-origin', // include, *same-origin, omit
 headers: {
  'Content-Type': 'application/json'
   // 'Content-Type': 'application/x-www-form-urlencoded',
 },
 redirect: 'follow', // manual, *follow, error
 referrerPolicy: 'no-referrer', 
  //// no-referrer, *no-referrer-when-downgrade, origin, 
  //origin-when-cross-origin, 
  //same-origin, strict-origin,
  //strict-origin-when-cross-origin, unsafe-url
 body: JSON.stringify(data) // body data type must match 
  //"Content-Type" header
});

Funkcja fetch przyjmuje jeszcze jeden parametr (opcje)

Wysyłanie danych

const data = { user: 'Agata' };

fetch('https://example.com/profile', {
  method: 'POST', // or 'PUT'
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify(data),
})
.then(response => response.json())
.then(data => {
  console.log('Success:', data);
})
.catch((error) => {
  console.error('Error:', error);
});

Funkcja fetch przyjmuje jeszcze jeden parametr (opcje)

async/await

const getData = async (url) => {
 const r = await fetch(url);
 const data = await r.json();
 console.log(data);
}

getData('https://pokeapi.co/api/v2/pokemon/pikachu');

Funkcja fetch możemy użyć korzystając z async/await.

ECMAScript 6+ przypomnienie

By noinputsignal

ECMAScript 6+ przypomnienie

  • 2