Ile tu jest błędów – rozwiązanie

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

Brak komentarzy!

Dodaj komentarz

Dozwolone tagi: <blockquote>, <code>, <strong>