I właśnie z tego powodu w JavaScripcie istnieje "jedyny słuszny" styl kodowania jeśli chodzi o klamry :). Nie powinno się ich stawiać od nowej linii!
A czemu tak się dzieje? Zjarek do końca tego nie wyjaśnił, bo wcale nie masz w kodzie "return;" -- nie masz tam średnika.
Dla ciekawskich -- szczegółówe wyjaśnienie...
Gramatyka JavaScriptu mówi jasno, że gdy chcesz coś zwrócić za pomocą instrukcji return coś
, to pomiędzy return
a coś
NIE WOLNO wstawiać znaku nowej linii. Zerknij do specyfikacji:
http://es5.github.com/#x12.9
Masz tam tylko dwie produkcje z return
:
-
return ;
-- czyli zwrócenie niczego (czyli zwrócenie undefined
).
-
return [no LineTerminator here] Expression ;
-- zwrócenie wartości wyrażenia. Za wyrażenie może robić liczba (5
), ciąg znaków ('abc'
), literał obiektowy { ads: 2}
itp. itd.).
Ty chciałeś wybrać opcję #2. Ale tam wyraźnie stoi napisane: no line terminator here
(znak nowej linii nie jest w tym miejscu dozwolony). Czyli: jeśli chcemy coś zwrócić, nie wolno wstawiać po return
znaku nowej linii!
I teraz co robi parser JS?
Próbuje korzystać z opcji #1, w której jest tylko samo return
i średnik. Ale... w Twoim kodzie nie ma średnika. Jest znak nowej linii, a po nim klamra otwierająca {
. Nie istnieje taka reguła w JavaScripcie, więc mamy błąd składni (klamra otwierająca nie jest dozwolona w tym miejscu).
JavaScript uruchamia więc procedurę automatycznego wstawiania średnika: http://es5.github.com/#x7.9
Jeśli chcesz poczytać specyfikację, to podpowiem tylko, że za "offending token" robi klamra otwierająca. Jeśli nie, to powiem w skrócie, co robi JavaScript: widząc, że po return
jest fragment kodu, który wg gramatyki języka nie powinien tam być, i że zaczyna się on od znaku nowej linii, wstawia zaraz za return
średnik. I sprawdza, czy teraz sytuacja się nie poprawiła.
Ta reguła jest troszkę zamotana. Można ją wyjaśnić też tak, że jeśli gdzieś potrzebny jest średnik, a zamiast niego mamy znak nowej linii, to ten średnik JavaScript sobie automatycznie wstawia.
Okazuje się, że wstawienie średnika za return
daje nam return;
, czyli poprawną instrukcję języka, zwracającą undefined
(pierwszą z dwóch reguł przytoczonych wyżej).
OK, linię z return
mamy odchaczoną. Parsujemy dalszą część kodu:
{
'asd': 2
};
Ten zaczynający się od klamry otwierającej ({
) kod dla większości z nas wygląda na literał obiektowy reprezentujący obiekt, który ma własność asd
o wartości 2
.
I w normalnych przypadkach dla JavaScriptu to też jest literał obiektowy! W uproszczeniu: klamra otwierająca, która NIE jest pierwszym nie-białym znakiem w instrukcji, oznacza początek literału obiektowego. W poniższych przypadkach mamy więc do czynienia z literałami obiektowymi:
return { // literał obiektowy, bo przed '{' mamy 'return '
asd: 3;
};
callback({ option: true }); // literał obiektowy, bo przed '{' mamy 'callback('
for (var prop in { a: 1, b: 2}) { // literał obiektowy, bo przed '{' mamy 'for (var prop in '
}
if ({} === {}) { // literał obiektowy, bo przed '{' mamy 'if ('
}
To pokrywa wszystkie normalne sytuacje. Zawsze, gdy literał ma się nam do czegoś przydać, przed nim musi znajdować się coś znaczącego, np. instrukcja if
, return
(ale bez znaku nowej linii!) czy wywołanie funkcji.
W naszym przypadku, ponieważ wcześniej za return
został automatycznie wstawiony średnik, mamy nową instrukcję, zaczynającą się od razu od klamry otwierającej. Wtedy jest inaczej: klamra otwierająca to nie początek literału obiektowego, tylko początek bloku kodu!
Bloków kodu to po prostu ciąg instrukcji zawartych pomiędzy znakami {
oraz }
:
{
instrukcja();
a = b + c;
inna_instrukcja();
}
Blok kodu można wstawić ot tak, wewnątrz innego bloku kodu, ale zwykle wstawiamy bloki np. po instrukcji if
, gdy chcemy warunkowo wykonać kilka instrukcji:
if (n === 0) {
instrukcja();
a = b + c;
inna_instrukcja();
}
Równie dobrze możemy napisać jednak tak:
przed_blokiem_kodu();
n = 2;
{
var i = 5;
pierwsza_instrukcja_w_bloku();
druga_instrukcja_w_bloku();
}
n++;
W JavaScripcie jednak nic nam to nie daje, bo (inaczej niż w C++) nie ma tu tzw. blokowego zakresu ważności zmiennych i zmienna i
jest zdefiniowana również przed blokiem kodu (i za nim też).
Niemniej jednak, możemy tak zrobić. Nawisy klamrowe z naszej kłopotliwej instrukcji return
stanowią własnie blok kodu.
Przytoczmy ten fragment jeszcze raz:
{
'asd': 2
};
No dobra. Ale co to za instrukcja 'asd': 2
? Czyli cośtam, zaraz za tym dwókropek, i dwójka?
No cóż...
JavaScript, jak wiele innych języków programowania, posiada etykiety (ang. label). Baardzo rzadko się z nich korzysta.
W innych językach, najczęściej używano etykiet razem z instrukcją skoku -- goto
:
// niepoprawny JS
var i = 0;
etykieta:
write(i);
i++;
if (i < 10) goto etykieta;
Definiowało się tam etykietę (tutaj nazwaną po prosty etykieta
) i w razie czego, można było tam skoczyć za pomocą goto
. JavaScript nie ma jednak goto
. Etykiet można w JS-ie użyć przy instrukcjach continue
i break
, ale w to się nie wgłębiajmy.
Etykietę można sobie nazwać prawie dowolnie, ale musi być to prawidłowy identyfikator JavaScript -- czyli nazwa nie rozpoczynająca się od cyfry i tak dalej, i tak dalej. Z grubsza, nazwa etykiety musi spełniać te same warunki co nazwa zmiennej.
Identyfikator etykieta
to poprawna nazwa etykiety. Identyfikator asd
też jest poprawny!
Mógłbyś napisać więc:
{
asd: 2
};
i byłoby dobrze!
Czyli w sumie, gdyby Twój kod wyglądał tak:
return
{
asd: 2;
};
nie byłoby błędu składni! Kod oznaczałby jednak coś innego, niż myślałeś. Za return
wstawiony byłby średnik, więc instrukcja return
zwracałaby undefined
. Za nią znajdowąłby się blok instrukcji. Blok zawierałby etykietę asd
oraz instrukcję wyrażenia, która byłaby po prostu dwójką (2
). Taką bezużyteczną dwójką. W JavaScripcie, i w wielu innych językach, możesz sobie napisać po prostu:
2 + 2;
i spowoduje to obliczenie wyniku dwa plus dwa, ale ten wynik nie zostanie nigdzie zapisany, ani nigdzie wypisany. Bezużyteczne, ale przydaje się w nieco innych sytuacjach. Równie dobrze jak 2 + 2
możesz sobie napisać samo 2
.
I taka dwójka stałaby w Twoim kodzie obok etykiety asd
, umieszczonej w bloku instrukcji zaraz za return
;).
Ale Ty nie masz etykiety asd
. Masz 'asd'
, z apostofami. Apostrofy nie mogą występować w nazwie etykiety, podobnie jak nie mogą być częścią nazwy zmiennej.
JavaScript wykrywa więc nieprawidłową etykietę! I stąd błąd "invalid label" :)