Pora (nie, nie będę liczył ile postów mogło ukazać się przez ten czas – za dużo…) na przedstawienie błędów z poprzedniej notki. Mam nadzieję, że się spodobało. Uważam to za jedno z bardziej przydatnych pytań na rozmowę kwalifikacyjną – potrafi bezstresowo wyłapać kandydata z dużą wiedzą na temat języka. Sprawdziłem w praktyce – chyba najlepiej odbierane zadanie. Do rzeczy jednak – mieliśmy za zadanie przeanalizować poniższy kod:
var foobar = {
property: Infinity,
enum = {
10th: "ten",
20th: "twenty"
},
toString: (function() {
return (NaN || foobar).toString();
})(),
copy: false && this.alert,
clone: function() {
this.__proto__ = foobar;
},
};
var obj = new foobar().clone();
Zróbmy to linijka po linijce.
var foobar = {
Inicjujemy object literal, czyli nic innego, jak gotowy (niepotrzebne jest tworzenie nowej instancji) obiekt.
property: Infinity,
Definiujemy pierwsza składową obiektu (dostęp do niej byłby realizowany poprzez foobar.property). Infinity to po prostu wartość w JS, oznaczająca nieskończoność (typu “number” – typeof Infinity === “number”).
enum = {
Kolejna składowa (będąca obiektem) o nazwie enum. Wszystko byłoby w porządku, gdyby nie JScript Microsoftu. Enum jest słowem zarezerwowanym przez ten język, więc wszelkie jego użycie w IE kończy się błędem! Warto też wiedzieć, że enum jest wyrażeniem zarezerwowanym (w kontekście przyszłych rozwiązań) przez standard ECMA, na którym jest oparty JavaScript. Poza tym zamiast = powinno być :.
10th: "ten",
Niestety własności obiektu ani nazwy zmiennych nie mogą zaczynać się od cyfry, więc mamy kolejny błąd.
20th: "twenty"
Jak wyżej.
},
Czysto, uff.
toString: (function() {
Definiujemy kolejną składową obiektu – nic wielkiego, warto jednak zwrócić uwagę na zapis (function() {})() – co wywołuje automatycznie funkcję podaną pomiędzy nawiasami (funkcję anonimową). Jeśli funkcja ta zwracałaby jakąś wartość, wtedy toString będzie się jej równać, inaczej będzie równy undefined (ponieważ nic nie zostanie zwrócone, a zmienne w JS domyślnie przyjmują taką wartość).
return (NaN || foobar).toString();
No i chcemy coś zwrócić. Zapis || jest skróconym zapisem poniższej konstrukcji warunkowej:
if (NaN) { return NaN; } else { return foobar; }
Jedna z fajniejszych cech języka. Oczywiście mogą się pojawić głosy o nieczytelności, ale będą to raczej skomlenia programistów przyzwyczajonych do innych języków – w JS konstrukcja ta jest szybsza i wydaje się bardziej naturalna (o ile oczywiście programujemy na co dzień w JavaScript).
Przechodząc do analizy tej linii mamy prosty warunek. Jako że NaN to jedna z falsy values i przy skonwertowaniu do wartości logicznej da false, JS spróbuje wziąć sobie zmienną foobar i na niej uruchomić funkcję toString. Ponieważ zmienna foobar w chwili uruchomienia anonimowej funkcji jest niewidoczna dla obiektu (undefined) – dostaniemy błąd.
Następnie mamy:
copy: false && this.alert,
Chcemy przypisać do copy… no właśnie, co? foobar = foo && bar to nic innego jak:
if (!foo) {
return foo;
}
return bar;
Jako że pierwsze jest false to szybko kończymy zabawę i copy przyjmuje wartość false właśnie. Warto wyróżnić komentarz mcv:
this.alert się nie wykona ze względu na “false &&”. Poza tym… nie wiadomo czy nie ma takiej właściwości w kontekście definiowania tej zmiennej (foobar). A nuż jest? „this” tutaj nie odwołuje się do foobar.
Pełna zgoda, popatrzmy na przykład, kiedy powyższa sytuacja występuje:
var fn = function() {
this.alert = "tadam!"
var obj = {
property: this.alert
}
return obj;
}
var foobar = new fn;
alert(foobar.property);
clone: function() {
Tworzymy funkcję clone. Nic wielkiego.
this.__proto__ = foobar;
W sumie wszystko w porządku – o ile obiekt w chwili jego inicjalizacji nie widzi foobar, tak funkcja wewnątrz obiektu owszem (a raczej będzie widziała w chwili użycia) – słowo klucz: closures. Gdyby składnia była bez błędów, można by napisać coś podobnego:
(new foobar.clone).__proto__.clone
},
W przypadku ostatniego elementu obiektu nie stawiamy przecinka. Firefox to toleruje, IE natomiast nie.
var obj = new foobar().clone();
foobar nie jest konstruktorem, a gotowym obiektem, więc nie możemy stworzyć instancji nowego obiektu korzystając z obiektu.
Gratuluję komentatorom – wielu z Was wysnuło bardzo ciekawe wnioski i uwagi!
Komentarze