Każdy JS developer powinien rozumieć podstawowe koncepcje dotyczące tego jakże złożonego języka. Poniższe zagadnienia to nie wszystko, ale można je potraktować jako fundamentalne i takie, które trzeba znać, aby się rozwinąć. Zaczynajmy!
Takim mianem określa się funkcję, którą wywołuje się zaraz po swoim utworzeniu. Jak można zdefiniować IIFE? Spójrz na poniższy przykład:
(() => console.log(‘Hello world’))();
W momencie, gdy wykonamy kod, w konsoli natychmiast pojawi się Hello World. IIFE używa się dla ochrony dostępu do zmiennych. Wtedy do zmiennych zdefiniowanych w IIFE nie można uzyskać dostępu z zewnątrz. Takie coś pomaga w pisaniu kodu, który łatwiej się utrzymuje i zwiększa szanse na uporządkowany kod źródłowy.
Takiej struktury używa się w prawie każdym języku programowania. Jest to popularny koncept, który przydaje się przy układaniu kodu w różne warstwy (np. data, view, logic) i traktowanie ich jako oddzielnych elementów.
W miarę, jak projekt się powiększa, to będziemy potrzebować struktury do bezproblemowego zwiększenia skali. MVC jest tutaj jednym z pewniejszych wyborów. Gdy będziesz dodawać nowe funkcje lub szukać błędów, to podziękujesz sobie za poświęcenie czasu na dodanie struktury Model-View-Controller. Naprawdę.
Używamy tego konceptu, gdy tworzymy funkcję wewnętrzną. Ma ona dostęp do zmiennych i parametrów funkcji zewnętrznej, nawet gdy funkcja zewnętrzna już zakończyła działanie.
Domknięcie pozwala na dostęp do danych wewnątrz funkcji bez potrzeby bezpośredniego modyfikowania ich. W taki sposób możesz chronić swój kod, dając innym możliwość rozbudowywania go - zwłaszcza gdy udostępniasz innym swoją bibliotekę.
const sayHelloTo = name => {
let message = ‘Hello ‘ + name;
return () => console.log(message);
}
const sayHelloToAmy = sayHelloTo(‘Amy’);
sayHelloToAmy(); // Hello Amy
Async/await umożliwia przetwarzanie asynchroniczne - takie coś dzieje się zazwyczaj przy wywoływaniu API. Dane muszą być w pełni pobrane, zanim pojawią się w widoku.
Async/await szczególnie przydaje się przy unikaniu piekła wywołań zwrotnych. Być może też tak jak ja, nie lubisz też zagnieżdżonego kodu - sprawia to, że jest on brzydszy i trudniejszy do utrzymania.
Poniżej przykładowe użycie async/await:
const displayData = async () => {
const data = await fetch(‘https://api.github.com/repositories');
const jsonData = await data.json();
console.log(jsonData);
};
displayData();
W JS mamy dwa rodzaje zakresu: lokalny i globalny. Wyobraź sobie, że zakres zmiennych jest jak krowa przywiązana liną do słupa - przez to porusza się tylko po ograniczonym terenie, co zależy od długości liny. Zmienna lokalna jest właśnie przywiązana krótką liną, a zmienna globalna w ogóle nie jest przywiązana i może sobie chodzić, gdzie chce.
Na przykład:
// Global scope
const globalCow = ‘global cow’;
const showCow = () => {
const localCow = ‘local cow’;
return globalCow;
};
const clonedCow = globalCow;
const mixedCow = globalCow + localCow; // error: Uncaught ReferenceError: localCow is not defined
Jak widać, zmienna globalCow może być używana w dowolnym miejscu, a nawet w lokalnym kontekście funkcji showCow. Nie możesz jednak używać zmiennej localCow poza funkcją showCow, ponieważ jest ona zdefiniowana lokalnie.
Nie jest tak łatwo po prostu przypisać wartość do zmiennej - musisz wiedzieć, czy masz do czynienia z wartościami, czy referencjami. W innym przypadku pewnie przez przypadek zmienisz wartości.
Wszystko jest proste, jeśli przypisujesz typy prymitywne, takie jak ciągi znaków, liczby, czy boolean - to wszystko są wartości.
Sprawy nieco się komplikują, jeśli zaczynasz przypisywać obiekty, tablice i funkcje. W takim wypadku mówimy o typach złożonych, które nie przechowują tak naprawdę konkretnych wartości, a referencje do wartości w pamięci.
Na przykład:
let num1 = 1;
let num2 = num1;
// Changing num2’s value does not change num1’s value
num2 = 4;
console.log(num1); // 1
console.log(num2); // 4
let arr1 = [‘Amy’, ‘John’];
let arr2 = arr1;
// Changing elements’ value in arr2 leads to changing elements’ value in arr1
arr2[0] = ‘Jane’;
console.log(arr1); // [“Jane”, “John”]
console.log(arr2); // [“Jane”, “John”]
Wywołanie zwrotne w JS to funkcja, która zostaje wykonana po wywołaniu innej funkcji. Funkcję wywołania zwrotnego można przekazać do innych funkcji jako parametr.
Po nam więc wywołania zwrotne? W normalnych warunkach pisany kod uruchamia się w sekwencji od góry do dołu. W innych przypadkach mamy też zadania, które trzeba wykonać przed pozostałymi - wtedy wywołania zwrotne się przydają.
const fetchUsers = callback => {
setTimeout(() => {
let response = ‘[{name: “Amy”}, {name: “John”}]’;
callback(response);
}, 500);
};
const showUsers = users => console.log(users);
fetchUsers(showUsers);
W powyższym przykładzie wywołujemy funkcję fetchUsers
i przekazujemy funkcję wywołania zwrotnego showUsers
jako parametr. Kiedy wszystkie dane są już załadowane, to showUsers
wyświetli je na ekranie.
Za każdym razem, gdy tworzymy funkcję lub obiekt w JavaScript, zostanie do nich dodany prototyp. Prototyp to obiekt domyślnie powiązany z funkcjami i obiektami, w którym możemy dołączyć dodatkowe właściwości, mogące być “dziedziczone” przez inne obiekty.
Na przykład:
function Person() {
this.name = ‘Amy’;
this.age = 28;
}
Person.prototype.job = ‘Programmer’;
Person.prototype.showName = function() {
console.log(‘My name is ‘ + this.name);
}
let person = new Person();
person.showName(); // My name is Amy
Przed ES6, w JavaScript nie było klas - można było jedynie definiować funkcyjne konstrukcje, które emulowały działanie klas.
function Book(title) {
this.title = title;
}
Book.prototype.showTitle = function() {
console.log(this.title);
};
let book = new Book(‘JavaScript’);
book.showTitle(); // JavaScript
Od ES6 można tworzyć rzeczywiste klasy, tak jak w innych językach, które mają klasy:
class Book {
constructor(title) {
this.title = title;
}
showBook() {
console.log(this.title);
}
}
let book = new Book(‘ES6’);
book.showBook();
Takie coś jest wygodne, ponieważ łączy kilka sposobów tworzenia klas w jedną.
To czysty sposób na wyodrębnienie właściwości z obiektów.
Podstawowe użycie:
const person = {
name: ‘Amy’,
age: 28
};
let { name, age } = person;
console.log(name); // Amy
console.log(age); // 28
Zmienne mogą być takie same, jak nazwy właściwości (jak powyżej). Możesz też zdefiniować nowe:
let { name: newName, age: newAge } = person;
console.log(newName); // Amy
console.log(newAge); // 28
Operator ten daje dostęp do wnętrza obiektu iterowalnego. Mówiąc krótko, jest to szybki i zwięzły sposób dodawania elementów do tablic, łączenia obiektów lub wyciągania poszczególnych elementów z tablicy, a następnie przekazywania ich do funkcji.
// Combining Arrays
let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];
let arr3 = […arr1, …arr2];
console.log(arr3); // [1, 2, 3, 4, 5, 6]
// Combining Objects
let obj1 = {
name: ‘Amy’,
age: 28
};
let obj2 = {
job: ‘programmer’
};
let obj3 = { …obj1, …obj2 };
console.log(obj3); // {name: “Amy”, age: 28, job: “programmer”}
// Spreading out an array and pass it to a function
const sum = (…arr) => {
const length = arr.length;
let sum = 0;
for (let i = 0; i < length; i++) {
sum += arr[i];
}
return sum;
};
let arr = [3, 5, 3, 2, 1];
console.log(sum(…arr)); // 14
console.log(sum(3, 5, 4, 1)); // 13
Czy wszystkie powyższe zagadnienia są jasne? Jeśli nie, to czas z nimi poćwiczyć i się rozwijać. Coś Wam jeszcze przychodzi do głowy? Dajcie znać w komentarzach!
Oryginał tekstu w języku angielskim możesz przeczytać tutaj.