Wypróbuj

Nozbe - get things done

i zwiększ swoją produktywność

Nienawidzę JavaScript’u! 4 najczęstsze błędy JavaScript jakie popełniasz

13

Dlaczego tak wielu programistów tak często rzuca tytułowym tekstem (któremu zwykle towarzyszą mniej parlamentarne przerywniki) wykonując gest jak na zdjęciu powyżej? Odpowiedzi pewnie nie trzeba daleko szukać, dlatego dziś postanowiłem przyjrzeć się kilku powodom, dla których pada tak wiele pomstowań i siarczystych przekleństw z tak wielu pokoi projektowych 🙂 Wybrałem 4 najczęstsze błędy JavaScript, bo niestety ale większość problemów związanych z pisaniem kodu w JavaScript wynika z nieznajomości niuansów tego języka – dotyczy to w szczególności osób na co dzień zajmujących się bardziej silnie typowanymi językami programowania…

1# Zakres zmiennych

To jeden z najczęściej popełnianych błędów – wyniesione z silnie typowanych języków założenie, że zakres zmiennych ogranicza się do bloku kodu zawartego pomiędzy dwoma nawiasami klamrowymi…

var x = 1;

if (x === 1) {
    var y = 2;
}

alert(y);

Tak wiem, przykład banalny ale dla niektórych może być mylący… W języku takim jak na przykład C#, powyższa operacja byłaby w ogóle niemożliwa – wystąpił by błąd już na etapie kompilacji. Ale nie w JavaScript! Tutaj wyświetlona zostanie liczba 2 ponieważ, w JavaScript zakres zmiennych jest zawsze zakresem funkcji – zmienna utworzona wewnątrz nawiasów klamrowych (za chwilę się dowiesz, że tak na prawdę wcale nie tam) jest więc widoczna również poza nią.

2# Przenoszenie deklaracji

Inną sprawą, o której należy pamiętać, jest coś co nazywamy przenoszeniem deklaracji (ang. hoisting). Otóż w JavaScript możliwe jest jak najbardziej stosowanie wielu rozrzuconych po całym kodzie instrukcji var jednak w rzeczywistości są one i tak traktowane jakby zostały zadeklarowane na początku. Zobaczcie:

function hoisting () {
    alert(x);
    var x = 'dupa';
    alert(x);
}

hoisting();

Powyższy kod najpierw wyświetli tekst undefined, a następnie dupa – spodziewać by się można, że zamiast tego powinien wystąpić wyjątek. Otóż nie, ponieważ nastąpiło przeniesienie deklaracji – powyższy kod jest więc tak na prawdę tożsamy z poniższym:

function hoisting () {
    var x;
    alert(x);
    x = 'dupa';
    alert(x);
}

hoisting();

Dlatego, aby uniknąć niepotrzebnego zaskoczenia, zawsze deklaruj wszystkie zmienne na początku funkcji! Dobrze jest też je od razu inicjować ponieważ, nie dzieje się to automatycznie tak jak w co poniektórych językach programowania.

3# Problemy z this

Ten problem nagminnie pojawia się m.in. podczas korzystania z funkcji wywołania zwrotnego będących jednocześnie metodami obiektu. Spójrzmy na taki przykład:

var test = {};
test.text = 'dupa',
test.showDeferred = function(){
    alert(this.text);
};

setTimeout(test.showDeferred, 100);

Powyższy kod wyświetli (z opóźnieniem) tekst undefined. Dlaczego? Ano dlatego, że liczy się kontekst wywołania, a w powyższym przykładzie tym kontekstem wywołania funkcji test.showDeferred jest przestrzeń globalna – funkcja setTimeout należy do tej przestrzeni i w niej wywołuje funkcje wywołania zwrotnego. Jednym z rozwiązań tego problemu może być wykorzystanie funkcji apply/call/bind:

var test = {};
test.text = 'dupa',
test.showDeferred = function(){
    alert(this.text);
};

setTimeout(test.showDeferred.bind(test), 100);

O tym problemie już kiedyś pisałem na blogu – warto też przejrzeć komentarze pod tamtym wpisem.

4# Operatory porównania

Czymś co sprawia wiele problemów są też oczywiście operatory porównania, które zachowują się trochę odmiennie od tych znanych z innych języków programowania. W JavaScript mamy do czynienia z niejawnym rzutowaniem podczas porównywania dwóch wartości za pomocą operatorów typu == czy !=, które każdy dobrze zna. Dlatego w ich przypadku typ zmiennej nie jest do końca istotny. Spójrzmy na przykład:

alert(false == '0');
alert(null == undefined);
alert("\t\r\n" == 0);
alert('' == 0);

Wszystkie powyższe instrukcje zwracają wartość true!! Dlatego w większości przypadków używaj operatorów takich jak === czy !== – one sprawdzają zarówno typ jak i wartość, a więc zachowują się tak jak prawdopodobnie się po nich spodziewasz.

4 najczęstsze błędy JavaScript – podsumowanie

Wybrałem tylko 4 najczęstsze błędy JavaScript żeby Was za bardzo nie zanudzić… Na bank znalazłoby się tego o wiele więcej. Ja jednak uważam, że większość narzekań na JavaScript bierze się przede wszystkim z niewiedzy na temat działania tego języka i chciałem pokazać, że wiele z tych problemów nie jest trudna do rozwiązania. Jeśli każdy znałby ten język lepiej i co ważne, akceptował jego odmienność, na pewno byłoby wszystkim łatwiej. Trzeba być tolerancyjnym przecież 😛

CHCESZ DARMOWEGO E-BOOKA?

Jeśli chcesz otrzymać mojego e-booka: Rozmowa Kwalifikacyjna - pytania z podstaw JavaScript zostaw mi swój e-mail:

Oprócz tego co poniedziałek dostaniesz maila z listą moich wpisów z poprzedniego tygodnia!

  • qiqqq

    Nie za bardzo rozumiem punkt 2#. W drugim przykładzie mamy zdefiniowaną, lecz niezainicjowaną zmienną „x” na początku funkcji. W rezultacie otrzymujemy jednak „undefined”. Piszesz, aby zawsze deklarować zmienne na początku funkcji, a tak właśnie jest w drugim przykładzie. Jaki jest więc w tym sens? Czy nie powinno być tak, że zawsze na początku funkcji powinniśmy deklarować i inicjować zmienną?

    • Bartłomiej Dybowski

      Drugi przykład pokazuje co się tak na prawdę dzieje w przykładzie pierwszym – jest o tym napisane wyraźnie w poście… I tak jak pisałem w tekście, oprócz deklaracji na początku funkcji powinniśmy również starać się zmienne inicjować od razu.

  • qiqqq

    Teraz rozumiem. Zastanawiałem się dlaczego zdefiniowana lecz niezainicjowana zmienna zwraca „undefined” skoro została zdefiniowana, a wychodzi na to, że zwracane „undefined” odnosi się nie do samej zmiennej a do wartości, którą ma reprezentować.

    • Bartłomiej Dybowski

      Tak. Zmienna, która nie ma żadnej wartości czyli jest niezainicjowana jest ‚undefined’. Wywołuje to często zaskoczenie u początkujących którzy spodziewają się raczej ‚null’ jednak ‚null’ to już jednak jakaś konkretna wartość.

  • Bardzo pomocny wpis… dziękuje…

  • vilgefortz

    Dokładnie – większość tych problemów bierze się z nieznajomości języka. To tak jakbym miał narzekać na php na początku mojej nauki, że wywołanie atrybutu klasy kropką prowadzi do ciekawych rezultatów 🙂

  • gg

    Kiedy będzie wznowiony cykl wpisów o wzorcach projektowych w C#?

    • Bartłomiej Dybowski

      Hej, raczej nie planuję wznawiać – blog raczej skręca w stronę JavaScript i nie wiem czy jeszcze pojawią się jakieś wpisy o C# w ogóle.

  • Cześć, dobrałeś ciekawe przykłady problemów. W przypadku przykładu 3. powinno być raczej bind, nie apply. apply i call wykonane zostaną od razu, nie zauważyłeś tego pewnie przez takie krótkie opóźnienie. (http://stackoverflow.com/a/15455043), mój jsbin: http://jsbin.com/tofisosolu/1/ . Co do porównań to JS jest pod tym względem koszmarny (https://twitter.com/konklone/status/475819362172280833), ciekawie z porównaniami robi się w Ruby.

  • Maniek

    Przeciez niuanse to nie problem, w wiekszosci sa bardzo logiczne. Moze poza brak closures ktory wada po prostu jest. Nienawidze js glownie za brak typowania i nieprzewidywalnosc api na roznych urzadzeniach. Do tego brak centralnej odpowiedzialnosci za jego rozwoj (w praktyce-zastoj) i niesamowity balagan w bibliotekach, frameworkach i transkompilatorach. 95% pomimu hypu do niczego sie nie nadaje! Niestety dopoki nie sprawdzisz-nie wiesz… Pozdrawiam.

  • ciachowski

    W trzecim punkcie zwracasz uwagę na kontekst wywołania, i bardzo dobrze.
    Przyrównałeś jednak do siebie trzy różne metody: apply, call oraz bind – warto by było bezpośrednio objaśnić, jakie dają możliwości i dlaczego się znacznie różnią – przede wszystkim apply/call od bind.
    Różnica ta pokazuje dobitnie błąd programistyczny popełniony w „poprawionym” przykładzie:

    setTimeout(test.showDeferred.apply(test), 100);

    Apply/call w momencie użycia wywołuje funkcję w określonym kontekście, przez co powyższy kod jest równoważny temu:

    test.showDeferred.apply(test);
    setTimeout(undefined, 100);

    Aby nie było odebrane to jako pusta krytyka – wystarczyłoby lekko zmodyfikować kod:

    setTimeout(test.showDeferred.bind(test), 100);


    http://devdocs.io/javascript/global_objects/function/bind
    http://devdocs.io/javascript/global_objects/function/apply
    http://devdocs.io/javascript/global_objects/function/call

    • Bartłomiej Dybowski

      Rzeczywiście, zwrócił już na to uwagę Michał w swoim komentarzu ale dzięki za czujność. Przykład poprawiony co do objaśnienia – Twój komentarz już to robi 🙂

  • Do tej listy dodałbym zarządzanie eventami. Czasem, jest tego tyle, że trzeba się dobrze zastanowić co ma być i kiedy ma być wykonane.