Powtórzenie wiadomości z fundamentów języka JavaScript
Dziś przejdziemy pokolei przez fundamenty języka JavaScript. Skoro przez cały kurs będziemy pisać w JavaScripcie, to dobrze jest mieć w miarę solide zrozumienie tego języka.
Kod powiązany z lekcją jest dostępny na GitHubie w tym repozytorium
Wideo towarzyszące
Specyfikacja języka JavaScript
Naczelnym dokumentem do którego będziemy się odnosić jest Specyfikacja Języka EcmaScript.
Co to jest EcmaScript i dlaczego o nim mówimy? Zabawna historia. Kiedy trzeba było opublikować specyfikację języka JavaScript, autorzy poszukiwali odpowiedniej grupy, która zgodzi się opublikować oficjalną specyfikację i dostali pozytywną odpowiedź od organizacji standaryzacyjnej ECMA. Problem w tym, że JavaScript na tym etapie jako nazwa należało do firmy, która nie koniecznie chciała się rozstać z rozpoznawalną marką, więc autorzy mieli język, którego nazwy nie mogli oficjalnie użyć. Dlatego opublikowali specyfikację pod nazwą EcmaScript, ale w mowie potocznej nikt tej nazwy nie używa. EcmaScript to oficjalna nazwa JavaScript. Jeden język, dwie nazwy.
Przyznaję, może historia nie była aż tak zabawna, ale już ona oddaje całkiem dobrze naturę JavaScriptu; ale nie zatrzymujmy się tutaj.
Konstrukcje warunkowe i pętle w JavaScript
Jak większość współczesnych języków programowania, JavaScript udostępnia nam klasyczne konstrukcje warunkowe i pętle.
Wklej ten kod do swojego edytora, przeanalizuj go, pozmieniaj i upewnij się, że rozumiesz, jak działają poszczególne konstrukcje. Kod jest też dostępny w moim repozytorium z kodem do tych zajęć, możesz sklonować to repozytorium korzystać.
let x = 5, y = 10;
if (x > y) { // warunki muszą być otoczone nawiasami
console.log(x, "jest większe niż", y);
} else {
console.log(x, "jest niewiększe niż", y);
}
while (x-- > 0) { // post-dekrementacja: sprawdź wartość a potem odejmij 1
console.log("W pętli while x ma wartość", x);
}
do {
console.log("Po pętli while x ma wartość", x);
} while (x > 0) // warunek nie jest prawdziwy
// ale pętla do..while i tak wykona się przynajmniej raz
// klasyczna pętla
// for (inicjalizacja; warunek wyjścia; inkrementacja)
for (let i = 0; i < y; ++i) {
if (i % 2 == 1) { // nieparzyste i
continue; // zacznij kolejną iterację pętli
}
console.log(i);
if ((i > 0) && (i % 2 == 0)) { // i jest większe od zera ORAZ parzyste i
break; // wyjdź z pętli
}
}
let greeting = "hello";
switch (greeting) { // porównaj greeting z poniższymi opcjami
case "hello":
console.log("angielski");
// brak wyrażenia "break". Potencjalny bug?
case "witaj": // JS pozwala na przejście pomiędzy przypadkami w switch'u
console.log("polski");
break;
default:
console.log("nieznany");
break;
}Jest jeszcze kilka innych konstrukcji, część z nich poznamy w kolejnych rozdziałach.
Zmienne i stałe danych w JavaScript
Istnieją trzy sposoby deklarowania danych w JavaScript:
varnajstarsza metoda która tworzy zmienne w obrębie pliku lub funkcjiletnowsza metoda, która pozwala tworzyć zmienne istniejące tylko w obrębie bloku koduconsttworząca wartości, których nie można nadpisać
Słowa kluczowe let i const zachowują się bardzo podobnie, z tą różnicą, że dane stworzone przy pomocy const mogą być tylko raz przypisane, zazwyczaj podczas deklaracji.
Przeanalizuj poniższy kod wraz z komentarzami, postaram się w nim wyjaśnić plusy i minusy tych sposobów deklaracji.
// var tworzy zmienną w zakresie globalnym jeżeli jest użyty poza funkcją
var global_var = "global1";
// var użyte w funkcji nie tworzy globalnych zmiennych
function doStuff() {
var local_var = "local1";
console.log(local_var);
}
// Poniższa linia spowodowałaby błąd
// console.log(local_var);
// Co może zaskoczyć, to że nazwy zmiennych zadeklarowanych przy pomocy var są dostępne w kodzie przed ich deklaracją
console.log(greeting);
var greeting = "hello from var";
console.log(greeting);
// let ogranicza czas istnienia zmiennej tylko do bloku kodu pętli
for (let i = 0; i < 3; i++) {
console.log("Zmienna i:", i);
}
// Poniższa linia spowodowałaby błąd
// console.log("i poza pętlą:", i);
// j będzie zmienną globalną!
for (var j = 0; j < 3; j++) {
console.log("Zmienna j:", j);
}
console.log("j poza pętlą:", j);
// Zmienne stworzone przy pomocy let zwrócą błąd przy próbie przedwczesnego dostępu.
// Poniższa linia spowodowałaby błąd
// console.log(salutation);
let salutation = "hello from let";
console.log(salutation);
// Zmienne o takich samych nazwach tworzone przy pomocy let "przesłaniać" (ang. shadowing), jeżeli są w innych blokach
{
let salutation = "hello from inner let";
console.log(salutation);
}
console.log(salutation);
// Zmienne tworzone przez var się nie przesłaniają, re-deklaracja zmiennej wskazuje na te same dane w pamięci
{
var greeting = "hello from inner var";
console.log(greeting);
}
console.log(greeting, "ooops");
// "stałe" zadeklarowane przy pomocy const nie pozwolą na ponowne przypisanie nowej wartości
const data = { message: "hello" };
console.log(data);
// Poniższa linia spowodowałaby błąd
//data = { message: "witaj };
// ALE TO NIE ZNACZY, ŻE TE DANE SĄ FAKTYCZNIE STAŁE
data.message = "witaj";
console.log(data);Typy danych w JavaScript
JavaScript definiuje zaledwie kilka typów bazowych:
- Undefined dla niezainicjalizowanych zmiennych
- Null dla pustych wartości
- Boolean dla wartości wyrażeń logicznych, tj. prawda/fałsz
- String dla łańcuchów znaków takich jak “hello” albo “你好”
- Numeric dla liczb, np. 3, 0x14, 3.1415, 1.3e-3. Numeric dzieli się na dwa podtypy:
- Number dla liczb zmiennoprzecinkowych i małych liczb całkowitych
- BigInt dla dużych liczb całkowitych
- Object dla złożonych obiektów, np.
{ "name": "Douglas", "age": 42 } - oraz najnowszy typ Symbol do tworzenia specjalnych identyfikatorów dla właściwości obiektów. Raczej nie będziesz potrzebować tego typu w tym kursie.
Aby poznać typ zmiennej albo wartości możemy użyć operatora typeof. Operator typeof przyjmuje jeden argument i zwraca string z nazwą typu podanego argumentu. Jeżeli chcemy sprawdzić typ wynikowy bardziej złożonego wyrażenia, warto użyć nawiasów obejmujących całe wyrażenie. W innym przypadku kolejność wykonywania operatorów w JS może dać nam inne wyniki niż spodziewane.
Pełen opis działania operatora typeof oczywiście znajduje się w specyfikacji
Zapisz poniższy kawałek kodu w pliku z kodem JavaScript i uruchom go:
console.log("*** Undefined ***")
let x;
console.log(typeof x); // x nie ma nadanej wartości => undefined
console.log(typeof y); // y nie zostało nigdy stworzone => undefined
// Tu napotykamy pierwsze bardzo dziwne zachowanie JS i operatora typeof
console.log("*** Null ***")
x = null;
console.log(typeof x) // Umm... 'object'?
console.log(typeof null) // Niestety tak mówi specyfikacja
console.log("*** Boolean ***")
x = true;
console.log(typeof x); // 'boolean'
console.log(typeof (x !== false)); // 'boolean'
console.log(typeof (typeof x === 'boolean')); // 'boolean'
console.log(typeof (5 >= 11)); // 'boolean'
console.log("*** String ***")
x = "What a lovely day!";
console.log(typeof "hello"); // 'string'
console.log(typeof x); // 'string'
console.log(typeof `Hello. ${x}`); // 'string'
console.log("*** Number ***")
x = 3;
console.log(typeof x); // 'number'
console.log(typeof 4.2); // 'number'
console.log(typeof (2 * Math.PI)); // 'number'
console.log(typeof (x + 1.2345e-4)); // 'number'
console.log("*** BigInt ***")
x = 3n; // tak tworzymy zmienne numeryczne typu BigInt
console.log(typeof x); // 'bigint'
console.log(typeof (x + 5n)); // 'bigint'
console.log("*** Object ***")
x = {}; // Tak tworzymy pusty obiekt w JS
console.log(typeof x); // 'object'
console.log(typeof { "name": "Douglas", "age": 42 }); // 'object'
// To były relatywnie oczywiste przykłady obiektów
// Ale są też mniej oczywiste
x = []; // to jest pusta tablica
console.log(typeof x); // 'object'?
console.log(typeof [1, 2, 4, 8]); // 'object'?
// A tu JavaScript po raz kolejny nas lekko zaskakuje
x = function() {};
console.log(typeof x); // 'function'? Skąd się nagle wzięło function?
// W powyższych przykładach zmienialiśmy typ zmiennej x przypisując
// do niej nowe wartości. Jeżeli utworzymy stałą używając słowa const
// podczas deklaracji, nie będziemy mogli przypisać do niej nowej wartości
const z = {};
// z = null; // To by wywołało błąd przy uruchamianiu
Analizując kod i wszystko co program drukuje na ekran, ciężko nie mieć przynajmniej kilku pytań.
- W linii 9 i 10 operator
typeofdla zmiennej i stałej o wartościnullzwraca nazwę typuobject. Tak niestety mówi specyfikacja i jest to zachowanie na tyle stare w języku, że gdyby to zmieniono, to na świecie jest sporo kodu, który w mniej lub bardziej subtelny sposób przestałby poprawnie działać. W przewidywalnej przyszłości to się nie zmieni i jeżeli chcemy wykryć wartość null w naszym kodzie, nie możemy polegać na operatorze typeof. - W liniach 46-47 operator
typeofdla tablic zwraca typobject, co jest prawidłowe. Tablice w JS to po prostu obiekty. Więcej na ten temat omówimy w rozdziale dedykowanym tablicom - W linii 51 podajemy do operatora
typeoffunkcję i w odpowiedzi dostajemy stringfunction, co ma z jednej strony sens, a z drugiej na liście typów powyżej nie mamy żadnej pozycji dotyczącej funkcji. Więc jak to jest? W dużym skrócie funkcje w języku JavaScript są przechowywane jako obiekty. Specjalne obiekty o specjalnych właściwościach, ale nadal obiekty. Nie ma dla nich osobnego typu pomimo tego, co mówi nam operatortypeof.
Obiekty w JavaScript
Jednym z podstawowych budulców naszych programów będą obiekty. Obiekty w JS są pojemknikami typu key-value (klucz-wartość), czyli możemy pod wybraną przez siebie nazwą umieścić w nich dowolną wartość. Już w poprzednich lekcjach widzieliśmy przykłady takich konstrukcji, jak i w powyższym pliku types.js.
Nazwy (klucze) których używamy do odnoszenia się do wartości przechowywanych w obiekcie to tzw. właściwości (properties), ale często używa się też angielskiej nazwy member albo field.
Załóżmy hipotetycznie, że tworząc aplikację dla biblioteki chciałbym zgromadzić informacje o autorze w jednym miejscu:
const author = { // Nawiasy klamrowe służą do stworzenia obieku w JS.
"first_name": "Douglas", // Nazwy właściwości mogą być podane jako string.
last_name: "Adams", // Albo jako identyfikator, jeżeli da się je zapisać jako
// ciąg dowzolonych znaków (głównie litrery alfabetu, cyfry, podkreślnik i znak dolara)
"books genre": "sci-fi,comedy", // Jeżeli chcemy użyć spacji w nazwie właściwości, musimy użyć stringu
alive: false, // Wartości przypisane do właściwości mogą być dowolnego typu
birth_date : { // Obiekty mogą nawet przechowywać inne obiekty!
day: 11,
month: 3,
year: 1952, // Przecinek po ostatniej właściwości jest opcjonalny
}
};
// Możemy odnośić się do wartości obiektu notacją <obiekt>.<właściwość>
console.log("Author:", author.first_name, author.last_name);
// albo notacji z nawiasami kwadratowymi: <obiekt>["<właściwość"]
console.log("Genres:", author["books genre"]);
// W przypadku właściwości ze spacją albo innymi niedozwolonymi znakami w nazwie, musimy użyć nawiasów
// console.log(author.books genre); // <- to by spowodowało błąd
// W przypadku obiektów zagnieżdżonych możemy korzystać z tych samych opcji dostępu
console.log("Born on", author.birth_date.day, ".", author.birth_date["month"], "in the year", author["birth_date"].year);
// Większość obiektów JS może być też zmieniana po stworzeniu
// nawet jeżeli zostały utworzone jako stałe
author.favorite_number = 42;
console.log(author); // JS zapewnia dość wygodne drukowanie obiektów zawierających dane
// Jeżeli chcę się dowiedzieć jakie właściwości zawiera obiekt, mogę użyć np. funkcji `Object.keys()` albo `Object.getOwnPropertyNames()`
console.log(Object.keys(author));
console.log(Object.getOwnPropertyNames(author));Tablice w JavaScript
Tablice w JS to również pojemniki na dane, ale tutaj kluczem do danych są liczby, konkretnie liczby nieujemne. Za każdym razem, kiedy w swoim programie stwierdzisz, że potrzebujesz pracować na więcej niż jednym obiekcie o podobnej strukturze, albo na szeregu liczb czy innych wartości, prawdopodobnie warto użyć tablic.
Kontynuując motyw z poprzedniego rozdziału, możemy użyć tablicy aby stworzyć listę książek danego autora
const grades = [ // Tablicę tworzymy przy pomocy nawiasów kwadratowych
4, 6, 3, 4, 5
];
console.log(grades[0]); // Do poszczególnych elementów możemy
// dostać się również używając notacji
// nawiasów kwadratowych.
console.log(grades[2]); // Indeksy elementów zaczynamy liczyć od zera
console.log(grades[5]); // Ponieważ mamy 5 elementów i indeksujemy od 0
// element o indeksie 5 nie istnieje
// Istnieje co najmniej kilka sposobów na przejście przez wszystkie
// elementy listy
// Można użyć zwykłej pętli for z własnym indeksem
for (let i = 0; i < grades.length; i++) { // właściwość "length" zwraca liczbę elementów w tablicy
console.log(grades[i]);
}
// Można użyć funkcji forEach, która jest właściwością tablicy
grades.forEach((grade, index, array) => { // musimy wtedy przekazać do forEach funkcję
console.log(`${index}) ${grade}`); // która będzie operować na elementach tablicy
});
// Możemy też użyć pętli "for .. of", ale wtedy nie mamy dostępu do indeksu
for (let grade of grades) {
console.log(grade);
}
// Możemy dodawać elementy do końca tablicy metodą "push"
grades.push(2);
console.log(`tablica grades ma teraz ${grades.length} elementów, jej ostatni element to ${grades[5]}`);
let popped = grades.pop();
console.log(`tablica grades ma teraz ${grades.length} elementów, usunięty element to ${popped}`);
// Można też dodawać i usuwać elementy z przodu tablicy metodami "unshift" i "shift", ale ze względu na konieczność przesuwania wszystkich elementów o jeden indeks nie jest to zalecana praktyka.
grades.unshift(popped);
console.log(`tablica grades ma teraz ${grades.length} elementów, jej pierwszy element to ${grades[0]}`);
// Tablice mogą zawierać elementy różnych typów, nawet tych bardziej złożonych
const random_stuff = [4, true, null, {message: "this is an object"}, ["to jest tablica w tablicy"], () => {console.log("I'm a function")} ];
console.log(random_stuff);
// Trzeba jednak uważać z tworzeniem zbyt skomplikowanych konstrukcji.
// Czasami skorzystanie z zawartości tablicy staje się przez to mało czytelne
random_stuff[5]();
console.log(random_stuff[4][0]);
console.log(random_stuff[3].message);
// Poniższa linijka ma identyczne znaczenie, jak powyższa
console.log(random_stuff[3]['message']);
// JavaScript pozwala też na tzw. destrukturyzację
// Poniższy zapis bierze pierwsze 3 elementy z tablicy random_stuff
// i umieszcza je w zmiennych a, b, c.
// Jak widać nie musimy w ten sposób przechwytywać wszystkich elementów.
let [a, b, c] = random_stuff;
console.log(a, b, c);
// A taki zapis bierze z tablicy 2 pierwsze elementy i umieszcza w zmiennych a i b
// a całą resztę tablicy umieszcza w zmiennej c
[a, b, ...c] = random_stuff;
console.log(a, b, c);O ile tablice mają swoją dedykowaną składnie i sprawiają wrażenie zupełnie innego bytu, pod spodem są to obiekty JavaScript. Możemy wyciągnąć nazwy ich wartości tak samo jak dla “normalnych” obiektów i podlegają tym samym zasadom działania.
const grades = [ 4, 6, 3, 4, 5 ];
console.log(Object.getOwnPropertyNames(grades));Funkcje w JavaScript
W poprzednich lekcjach już trochę mówiliśmy o funkcjach, ale przyjrzyjmy się im bliżej.
// deklaracja funkcji
function myFunction() {
console.log("In my function");
}
// wywołanie funkcji
myFunction();
// Funkcja może przyjmować wartości podczas wywołania oraz zwracać wartości na zewnątrz
// To jest deklaracja funkcji z jednym parametrem o nazwie 'number'
function multiplyByTwo(number) {
return number*2;
}
console.log("Wywołanie z argumentem 2 daje wynik", multiplyByTwo(2));
// JavaScript podchodzi dość luźno do wywołań funkcji w kwestii liczby argumentów
console.log("Wywołanie bez argumentu daje wynik", multiplyByTwo());
console.log("Wywołanie z argumentami 3, 4, 5 daje wynik", multiplyByTwo(3, 4, 5));
// Dzięki temu mamy sporą swobodę w pisaniu funkcji obsługujących różne zestawy parametrów
// ale musimy pamiętać, że kompilator nas nie uchroni przed wywołaniem funkcji z nieoczekiwanymi
// argumentami. Dlatego możemy użyć kilku sztuczek.
// Możemy nadawać parametrom domyślne wartości
function multiplyByTwoProtected(number = 0) {
return number*2;
}
console.log("Wywołanie z argumentem 2 daje wynik", multiplyByTwoProtected(2));
console.log("Wywołanie bez argumentu daje wynik", multiplyByTwoProtected());
// Możemy powiedzieć JS, że chcemy aby wszelkie dodatkowe parametry
// zadeklarowane po spodziewanych były umieszczone w tablicy o wybranej przez
// nas nazwie (tutaj 'rest')
function getAllTheArgumentsAfterFoo(foo, ...rest) {
console.log("Pierwszy argument:", foo);
console.log("Cała reszta", rest);
}
getAllTheArgumentsAfterFoo("foo", "bar", "baz");
getAllTheArgumentsAfterFoo("foo");
getAllTheArgumentsAfterFoo();
// Albo możemy wykorzystać niejawny parametr 'arguments'
function getAllTheArguments(foo) {
if (arguments.length > 1) {
console.log(foo, arguments[1]);
} else {
console.log(foo, "and no additional arguments provided");
}
}
getAllTheArguments("foo", "bar", "baz");
getAllTheArguments("foo");
getAllTheArguments();
// Ważna uwaga, funkcje deklarowane jako "arrow functions" nie mają dostępu do niejawnego "arguments"
const thisWontWork = (foo) => {
console.log(foo, arguments);
}
// Poniższa linia zadziała, ale wydrukuje argumenty przekazane przez Node.js
// dla wykonania całego pliku... ciekawi mogą sprawdzić, co się tam znajduje
// thisWontWork("foo", "bar", "baz");
// Funkcje mogą też zmieniać wartości przekazane podczas wywołania
// o ile są to dane przekazywane jako referencje (w uproszczeniu obiekty, tablice, etc.)
function annotateObject(object, number, string) {
object.comment = "note"; // to zmieni argument poza funkcją
number = 42; // to NIE wpłynie na dane poza funkcją
string = "set in function"; // to NIE wpłynie na dane poza funkcją
}
let myObject = { message: "hello" };
let myNumber = 3;
let myString = "set globally";
annotateObject(myObject, myNumber, myString);
console.log(myObject, myNumber, myString);
// Funkcje mogą być też umieszczone na obiektach
// W takiej sytuacji mówimy o takiej funkcji jako o "metodzie" obiektu
// Taka funkcja może korzystać ze słowa kluczowego "this" aby odnosić się do
// obiektu, na którym jest umieszczona
let greeter = {
name: "Bob",
greet: function () { console.log("Hi, I am", this.name) },
}
// Wywołanie metody obiektu
greeter.greet();
greeter.name = "Alice"
greeter.greet();
// Alternatywna składnia dla deklarowania metod
let loud_greeter = {
name: "Bob",
greet() { console.log("HI, I AM", this.name.toUpperCase()) },
}
loud_greeter.greet();
loud_greeter.name = "Alice"
loud_greeter.greet();
// I znów uwaga na temat arrow functions: nie mają one dostępu do słowa kluczowego "this"
// więc nie mogą być używane jako metody na obiektach
let bad_greeter = {
name: "Bob",
greet: () => { console.log("Hi, I am", this.name) },
}
bad_greeter.greet();Obiektowość i dziedziczenie w JavaScript
JavaScript dla ludzi przyzwyczajonych do statycznych systemów typów zachowuje się w najlepszym wypadku nieprzewidywalnie. Jeżeli jednak się nad tym pochylić i spróbować zrozumieć, jak koncepcja dziedziczenia jest zaimplementowana w tym języku, to nieprzewidywalność szybko znika.
JavaScript implementuje tzw. łańcuch prototypów. Już z niego odrobinę korzystaliśmy, choć o tym nie wiemy.
Uruchom ten kod w swoim środowisku i zastanów się nad tym, skąd się bierze metoda toString() na naszym obiekcie.
let obj = { message: "hello" };
let string_representation = obj.toString();
console.log(string_representation);Nie zadeklarowaliśmy tej metody, a jednak wydaje się, że takowa istnieje i JS wywołał ją w jakiś sposób.
Przejdź przez poniższy kod i komentarze i postaramy się wspólnie rozwikłać tę zagadkę.
// JavaScript pozwala na tworzenie obiektów metodą Object.create()
// Ale wymaga podania wtedy "prototypu". Póki co podajmy "null".
let empty = Object.create(null);
// Możemy z nowo stworzonego obiektu korzystać jak z każdego innego obiektu
empty.property = "value";
// Ale zostanie wydrukowany trochę inaczej
console.log(empty);
// Nie ma dostępu np. do metody toString
// Poniższa linijka wywoła błąd
// console.log(empty.toString());
// Kiedy tworzymy obiekt używając standardowej składni, tak naprawdę JavaScript
// pod spodem ustawia prototyp nowego obiektu na "Object.prototype"
let normal = Object.create(Object.prototype);
console.log(normal);
// Stwórzmy sobie prosty obiekt
let base = { a: 1, b: 2 };
// A teraz podajmy nasz nowo stworzony obiekt jako prototyp kolejnego
let derived = Object.create(base);
// Możemy normalnie dodawać do niego właściwości
derived.c = 3;
derived.d = 4;
// Wydrukowanie na konsolę działa tak jak się tego spodziewamy
console.log(derived);
// Ale dlaczego nasz obiekt ma właściwość "a"?
console.log(derived.a);
// Kiedy JS próbuje znaleźć właściwość na obiekcie, zaczyna od samego obiektu.
// Jeżeli nie ma takiej właściwości, JS sprawdza prototyp obiektu.
// Jeżeli nadal właściwość nie istnieje, idzie dalej... tak daleko, jak sięga
// łańcuch prototypów.
// Jeżeli na żadnym etapie nie znajdzie poszukiwanej właściwości, wtedy JS
// zgłosi błąd.
//
// derived ----> base ----> Object.prototype ----> null
// c: 3 a:1 toString(), itd.
// d: 4 b:2
// W tym przypadku skoro obiekt "derived" nie miał właściwości "a", JS poszedł
// o krok dalej i znalazł ją na obiekcie "base"
// W ten sam sposób wcześniej JS znalazł metodę toString, która istnieje na
// obiekcie Object.prototype
// Wracając do jednego z poprzednich przykładów, możemy stworzyć sobie teraz
// obiekt, który będzie zawierał wspólne funkcjonalności dla wielu innych obiektów
let greeter_base = {
name: "<no name>",
greet() { console.log("Hello, I'm", this.name) },
}
function make_greeter(name) {
let greeter = Object.create(greeter_base);
if (name != null) {
greeter.name = name;
}
return greeter
}
let john = make_greeter("John");
let alice = make_greeter("Alice");
let anonymous = make_greeter();
john.greet();
alice.greet();
anonymous.greet();
// Ten sam efekt możemy osiągnąć tworząc funkcję konstruującą obiekty i używając operatora "new"
function Greeter(name) {
if (name != null) {
// Przy użyciu operatora "new"
// Nowo tworzony obiekt jest podpięty pod słowo kluczowe "this"
this.name = name;
}
}
// w przypadku operatora "new", JS pod spodem dla nowo tworzonego obiektu wywoła funkcję Object.create
// z argumentem "Greeter.prototype", więc tam musimy umieścić wspólne funkcjonalności
Greeter.prototype = {
name: "<no name>",
greet() { console.log("Hi there, I'm", this.name) },
}
let bob = new Greeter("Bob");
let eve = new Greeter("Eve");
let anon = new Greeter();
bob.greet();
eve.greet();
anon.greet();
// I jeszcze raz ten sam efekt możemy uzyskać używając słowa kluczowego "class"
class GreeterClass {
constructor(name) {
if (name != null) {
this.name = name;
}
}
name = "<no name>";
greet() { console.log("Hi! I'm", this.name) }
}
let ben = new GreeterClass("Ben");
let lisa = new GreeterClass("Lisa");
let noname = new GreeterClass();
ben.greet();
lisa.greet();
noname.greet();Na chwilę obecną tyle podstaw nam wystarczy. W razie potrzeby będziemy dokonywać kolejnych eskapad w tajniki JavaScriptu, ale chyba i tak przerobiliśmy dziś dość materiału.