Skocz do treści

Już wkrótce odpalamy zapisy na drugą edycję next13masters.pl. Zapisz się na listę oczekujących!

2. Weekly JavaScript Challenge

Dobiega końca drugi Weekly JavaScript Challenge, którego jestem organizotorem. Jeśli jeszcze nie wiesz o co chodzi, to już wyjaśniam! Jest to grupa na Facebooku, na której mniej-więcej raz w tygodniu pojawia się nowe zadanie do wykonania w JavaScripcie. Celem istnienia grupy jest wzajemna pomoc i nauka JS-a. A wszystko to pro bono, dla powszechnego dobra i całkowicie za darmo. Zachęcam wszystkich początkujących programistów do spróbowania swoich sił w zadaniach!

Zdjęcie Michał Miszczyszyn
Dobry Kod10 komentarzy

W 2. Weekly JavaScript Challenge udział wzięło 14 osób. Trzymamy równy poziom :) Przy okazji chciałem bardzo serdecznie podziękować wszystkim początkującym programistkom i programistom, którzy z ogromnym zapałem rozwiązują kolejne zadania, a także pomagają i komentują prace innych. Dzięki!

Pamiętajcie, że w trakcie trwania kolejnych „czelendży” nadal możliwe jest wrzucanie rozwiązań do poprzednich zadań!

Podsumowanie

Ponownie udało mi się zidentyfikować kilka ogólnych problemów, które się powtarzają. Poniżej krótkie podsumowanie, w którym starałem się zawrzeć bardzo ogólne porady i dobre praktyki odnośnie pisania JavaScriptu, a także najczęściej popełnianie błędy. Zapraszam do czytania!

Matematyka w JavaScripcie

W wielu miejscach w Internecie można natknąć się na zaskoczone osoby pytające dlaczego JavaScript nie umie dodawać:
0.1 + 0.2 // 0.30000000000000004  
Co? Ale jak to 0.30000000000000004?

Nie jest to żaden szczególny wymysł twórców JavaScriptu, ani tym bardziej błąd w implementacji. Jest to rezultat podążania za specyfikacją IEEE 754, która definiuje w jaki sposób należy przeprowadzać operacje na liczbach zmiennoprzecinkowych i „problem” ten nie dotyczy tylko JavaScriptu.

Z tego względu nie należy nigdy przeprowadzać obliczeń finansowych na liczbach zmiennoprzecinkowych w JS. Służą do tego specjalne biblioteki takie jak na przykład BigInteger.js. Więcej na ten temat można doczytać na stronie o zabawnym adresie 0.30000000000000004.com

Zdarzenia change i input

Nasłuchiwanie na zdarzenie change na elemencie <input> albo <textarea> nie działa dokładnie tak, jak wiele osób by oczekiwało. Zdarzenie to jest wysyłane dopiero w momencie, gdy zmiany zostały zakończone – czyli np. po opuszczeniu inputa. Aby być informowanym o zmianie każdej literki lepiej podpiąć się pod zdarzenie input: Zobacz Codepen.

Wzorce i antywzorce

Pisałem już o zasadzie jednej odpowiedzialności w poprzednim podsumowaniu. Napisałem tam, że ważne jest, aby funkcje wykonywały jedno i tylko jedno zadanie. Słusznie. Jednak podążanie tylko za tą jedną zasadą nie czyni jeszcze kodu idealnym. Dobrych wzorców programowania jest co najmniej kilka, więcej można poczytać np. o SOLID albo o code smells wg. Jeffa Atwooda.

Przykładowo: Można świetnie rozwiązać ten challenge zgodnie z Single Responsibility Principle, ale gdyby chcieć do aplikacji później dodać nową jednostkę, wymagana byłaby modyfikacja w więcej niż jednym miejscu, gdyż lista jednostek jest przechowywana w HTML-u, ale ich wartości już w kodzie JS. Rozwiązanie? Zmodyfikować kod w taki sposób, aby zmiana jednej funkcji aplikacji wymagała zmiany tylko jednego miejsca w kodzie.

Lakoniczne nazwy

Konfucjusz podobno powiedział, że „mądrość zaczyna się od prawidłowego nazwania rzeczy”. W zasadzie to nigdy nie dowiemy się, czy te słowa naprawdę padły z jego ust, czy tylko miał bystrego PR-owca, niemniej jednak to zdanie sprawdzi się jako świetny wzorzec pisania czytelnego kodu.

Spójrzmy na przykłady źle nazwanych funkcji i zmiennych:

var userString = 'michal';  
var user_name = 'michal';

function isNumber(x) {  
    if (!Number.isFinite(x)) {
        alert('Error!');
    }
}

function toMetres(a, b) {  
    return a * b;
}

class FileHandle {  
    openFile() {}
    close() {}
}

Po kolei:

  • userString – nazwa zawiera w sobie typ. To niepotrzebne, nie przekazuje żadnej dodatkowej informacji, a do tego jeśli typ miałby się zmienić to musielibyśmy zmienić również nazwę zmiennej
  • user_name – tutaj w zasadzie nie ma problemu w samym nazewnictwie tej zmiennej, ale w kontekście całego kodu widoczna jest niekonsekwencja – spójniej byłoby nazwać ją userName
  • isNumber – funkcje, który nazwy zaczynają się od “is” lub “has” zwyczajowo powinny coś sprawdzać i zwracać true lub false – to nawet brzmi naturalnie. Tutaj isNumber dokonuje walidacji i wyświetla błąd, więc nazwa nie jest odpowiednia.
  • toMetres – nazwa całkowicie nie oddaje tego, co ta funkcja robi! Nazwy funkcji powinny być czasownikami, wtedy znacznie lepiej przekazują intencje programisty.
  • FileHandle – nazwa tej klasy jest okej, chodzi mi o jej metody: openFile i close. To bardzo niespójne! Jeśli widzimy funkcję o nazwie openFile to można oczekiwać, że będzie również funkcja closeFile. Tutaj jednak nazywa się ona tylko close.
Ogółem: nie bójmy się dłuższych i bardziej opisowych nazw! Programowałem trochę w iOS (Objective-C) gdzie nazywa funkcji są niezwykle rozwlekłe. Nabyłem w tym środowisku kilku nawyków. I tak na przykład ja funkcję toMetres zamieniłbym na calculateMetresFromUnits lub podobną, równie długą.

this

Och, to nieszczęsne this w JavaScripcie. Nierozumiane i niekochane przez nikogo. Mi samemu zajęło bardzo dużo czasu, aby poznać i zrozumieć niuanse z nim związane. W skrócie: funkcje wywoływane są w kontekście. Kontekst może się zmieniać. Trzeba o tym pamiętać. To tyle. To naprawdę tylko tyle i aż tyle. Spójrzmy na przykładowy kod:
'use strict';
const obiekt = {
a: 123,
getA() {
return this.a;
}
};
const inneGetA = obiekt.getA;

obiekt.getA(); // 123
inneGetA(); // ??

Co się stanie, gdy wywołamy naszą funkcję inneGetA? Wiele osób instynktownie uznaje, że inneGetA jest tylko referencją na obiekt.getA i dlatego powinno zwrócić wartość 123. Tak się jednak nie dzieje. inneGetA rzeczywiście „wskazuje” na getA, jednak wywołanie inneGetA() odbywa się już w innym kontekście. Stąd otrzymujemy błąd: Uncaught TypeError: Cannot read property 'a' of undefined. Sprawa jest jeszcze ciekawsza, gdyż kontekst można zmieniać:

inneGetA.call({a: 1}) // 1  
inneGetA.apply({a: 2}) // 2  
inneGetA.bind({a: 3})() // 3  

Więcej na ten temat można doczytać w tym wpisie:

https://typeofweb.com/this-js-kontekst-wywolania-funkcji/

this w JS — czyli kilka słów o kontekście wywołania funkcji

Czy kiedykolwiek spotkałaś(-eś) się z błędem w aplikacji, który wynikał z tego, że "this" było ustawione na coś innego, niż się spodziewałaś/eś? Jeśli tak, to nie jesteś jedyna(-y). W swojej karierze programisty miałem okazję występować w roli rekrutera na ponad 160-ciu rozmowach kwalifikacyjnych na stanowiska front-endowe. Jeśli nauczyło mnie to…

this i addEventListener

I teraz sedno tego akapitu. Co zrobi poniższy kod?
const App = {  
    displayMessage() {
        console.log('dziala!');
    },
    handleInputChange() {
        this.displayMessage();
    }
};

input.addEventListener('change', App.handleInputChange);

Oczywiście rzuci błąd Uncaught TypeError: this.displayMessage is not a function. Funkcja handleInputChange wywoływana jest w innym kontekście – a więc this.displayMessage nie istnieje. Jedno z możliwych rozwiązań to użycie bind:

input.addEventListener('change', App.handleInputChange.bind(App));  

Na koniec

Zachęcam do wzięcia udziału w kolejnym Weekly JavaScript Challenge! Ja też się całkiem sporo teraz uczę dzięki uczestnikom i myślę, że każdy może wynieść coś dla siebie nawet z najprostszych zadań.

👉  Znalazłeś/aś błąd?  👈Edytuj ten wpis na GitHubie!

Autor