[{"content":"Gdzie się kończą granice, a jednak nadal mają swój ciąg - tam zaczyna się nieskończoność! Ale żeby dokładniej wiedzieć, z czym mamy do czynienia, użyjmy operatora typeof, aby dowiedzieć się z jakim typem danych mamy do czynienia!\ntypeof Infinity // \u0026#34;number\u0026#34; Przyjrzyjmy się pierwszemu przypadkowi Infinity\n1 / 0 //Infinity 1 / -0 //-Infinity Dlaczego tak się dzieje?\nTo wynika z matematycznej koncepcji granic:\n$$\\lim_{x \\to 0^+} \\frac{1}{x} = +\\infty$$$$\\lim_{x \\to 0^-} \\frac{1}{x} = -\\infty$$Gdy dzielimy przez liczbę coraz bliższą zeru od strony dodatniej ($0^+$), wynik rośnie do $+\\infty$.\nGdy dzielimy przez liczbę coraz bliższą zeru od strony ujemnej ($0^-$), wynik maleje do $-\\infty$.\nJavaScript rozróżnia +0 i -0, dlatego 1/0 daje Infinity, a 1/-0 daje -Infinity.\nJak widać - nie trzeba było się zbytnio natrudzić, aby zobaczyć ten przypadek w akcji.\nSpróbujmy jej zatem poszukać :)\nlet value = 1; while (value !== Infinity) { let next = value * 2; if (next === Infinity) { console.log(\u0026#39;Ostatnia wartość:\u0026#39;, value.toExponential()); console.log(\u0026#39;Następna wartość:\u0026#39;, next); break; } value = next; } Output:\nOstatnia wartość: 8.98846567431158e+307 Następna wartość: Infinity Jak widać - znalezienie nieskończoności nie jest takie trudne - JavaScript nam to znacznie ułatwia podając na tacy wartość maksymalną przed wkroczeniem w Infinity zamiast żmudnego i niewydajnego iterowania:\nNumber.MAX_VALUE Output:\n1.7976931348623157e+308 Co 1.7976931348623157e+308 przed nieskończonością (Infinity) reprezentuje?\n179,769,313,486,231,570,000,000,000,000,000,000,000,000,000, 000,000,000,000,000,000,000,000,000,000,000,000,000,000,000, 000,000,000,000,000,000,000,000,000,000,000,000,000,000,000, 000,000,000,000,000,000,000,000,000,000,000,000,000,000,000, 000,000,000,000,000,000,000,000,000,000,000,000,000,000,000, 000,000,000,000,000,000,000,000,000,000,000,000,000,000,000, 000,000,000,000,000,000,000,000,000,000,000,000,000,000,000, 000,000,000,000,000 To niewyobrażalnie wielka liczba, większa niż liczba atomów we wszechświecie $\\sim 10^{80}$ czy liczba możliwych partii szachowych $\\sim 10^{120}$.\nDlaczego zatem taka wartość? W czasach definiowania standardu w 1985 roku pamięć RAM była droga, a wybór 64 bitów dla double to świadoma decyzja projektowa. Ta wartość wystarczy do obliczeń inżynierskich, większości symulacji fizycznych, astronomii i w finansach.\nTak więc to był wystarczający kompromis dla dobrej precyzji (15-17 cyfr), efektywnej dla sprzętu i uniwersalnej (wspieranej przez każdy procesor).\nStandard IEEE 754 jako regulator Infinity W standardzie IEEE 754 zostały zdefiniowane zarówno NaN, jak i Infinity, więc to nie jest tylko feature JavaScript, ale również innych języków programowania!\n#include \u0026lt;iostream\u0026gt; #include \u0026lt;limits\u0026gt; int main() { double max = std::numeric_limits\u0026lt;double\u0026gt;::max(); double inf = std::numeric_limits\u0026lt;double\u0026gt;::infinity(); if (inf \u0026gt; max) std::cout \u0026lt;\u0026lt; inf \u0026lt;\u0026lt; \u0026#34; is greater than \u0026#34; \u0026lt;\u0026lt; max \u0026lt;\u0026lt; \u0026#39;\\n\u0026#39;; } Output:\ninf is greater than 1.79769e+308 Dlaczego zamiast liczby został wypisany inf?\nInfinity to wartość, którą da się przedstawić bitowo, ale która nie reprezentuje żadnej konkretnej wartości liczbowej - zamiast tego reprezentuje koncepcję matematycznej nieskończoności. Zatem jeśli ktoś by chciał wypisać surowe bity to w odpowiedzi otrzymałby\n+Infinity = 0x7FF0000000000000 -Infinity = 0xFFF0000000000000 inf jest po prostu czytelniejsze i wygodniejsze od reprezentacji bitowej, tak samo jak Infinity w JavaScript.\nPo co więc ten koncept został wprowadzony w standardzie IEEE 754? Zamiast przerywać działanie programu przy dzieleniu przez zero lub overflow, IEEE 754 daje wartość, z którą można dalej pracować:\nconst result = calculate_something(); if (result === Infinity) { console.error(\u0026#34;Wartość za duża!\u0026#34;); return Number.MAX_VALUE; } Bez Infinity - crash lub losowe zachowanie.\nRównież dzięki Infinity zachowujemy matematyczną poprawność\n1 / Infinity === 0 // lim(1/x) gdy x→∞ Infinity + 5 === Infinity // ∞ + c = ∞ Infinity * Infinity // ∞ × ∞ = ∞ Ale uwaga - niektóre operacje z Infinity dają NaN (nieokreślone):\nInfinity - Infinity // NaN (∞ - ∞ jest nieokreślone) Infinity * 0 // NaN (∞ × 0 jest nieokreślone) Infinity / Infinity // NaN (∞ / ∞ jest nieokreślone) Przykładowe zastosowania Wyłączenie timeout:\nconst config = { timeout: debugMode ? Infinity : 5000 // brak timeout w debug }; setTimeout(() =\u0026gt; { console.log(\u0026#39;To się nigdy nie wykona\u0026#39;); }, config.timeout); Uwaga: Niektóre przeglądarki limitują timeout do 32-bit int (~24 dni max). Infinity może zostać przekonwertowane do tej wartości.\nSortowanie:\nconst items = [ { name: \u0026#39;Alice\u0026#39;, priority: 1 }, { name: \u0026#39;Bob\u0026#39;, priority: 2 }, { name: \u0026#39;System\u0026#39;, priority: Infinity } // Zawsze na końcu ]; items.sort((a, b) =\u0026gt; a.priority - b.priority); Links:\nhttps://en.wikipedia.org/wiki/IEEE_754 ","permalink":"https://pzarycki.pl/posts/infinity/","summary":"1/0 nie crashuje programu? Dlaczego Number.MAX_VALUE to 10^308? Zajrzyjmy pod maskę IEEE 754 i odkryjmy, czym naprawdę jest nieskończoność w JavaScript.","title":"Infinity - gdzie kończy się JavaScript?"},{"content":"Obiekt obiektowi nierówny W tym poście zajmiemy się czymś, co jest często spotykane w językach programowania - operatorami porównania.\nW JavaScript, jak i w innych językach programowania, mamy różne typy danych, które możemy do siebie przyrównywać:\n\u0026#34;test\u0026#34; === \u0026#34;test\u0026#34; // true true === true // true 1 === 1 // true Ale nie wszystko jest takie oczywiste, co podpowiadałaby nam logika:\n{} === {} // false 🤔 [] === [] // false 🤔 Dlaczego tak się dzieje?\nTypy w JavaScript JavaScript jest językiem dynamicznie typowanym, w związku z tym typy są \u0026ldquo;otagowywane\u0026rdquo; w pamięci - no bo skądś musi być wiadomo co to jest. To niejako zwalnia programistę JavaScript z myślenia, jaki typ aktualnie jest alokowany.\nWyróżniane są dwa główne typy:\nPrymitywny - niemutowalny, porównywany przez wartość Referencyjny - mutowalny, porównywany przez referencję | Typ | Immutable? | Przechowywanie | Porównanie | Przykład | |--------------------|------------|-------------------|-------------------|-----------------------------| | Number (Int32) | ✅ TAK | Stack (inline) | Przez wartość | 42 === 42 → true | | Number (Float) | ✅ TAK | Heap (HeapNumber) | Przez wartość | 3.14 → niemożliwa mutacja | | String | ✅ TAK | Heap (JSString) | Przez wartość* | \u0026#34;hello\u0026#34; === \u0026#34;hello\u0026#34; → true | | Boolean | ✅ TAK | Stack (inline) | Przez wartość | true === true → true | | undefined | ✅ TAK | Stack (inline) | Przez wartość | undefined === undefined | | null | ✅ TAK | Stack (inline) | Przez wartość | null === null → true | | Symbol | ✅ TAK | Heap (Symbol) | Przez referencję | Symbol() !== Symbol() | | BigInt | ✅ TAK | Heap | Przez wartość* | 10n === 10n → true | |--------------------|------------|-------------------|-------------------|-----------------------------| | Object | ❌ NIE | Heap (JSObject) | Przez referencję | {} !== {} | | Array | ❌ NIE | Heap (JSObject) | Przez referencję | [] !== [] | | Function | ❌ NIE | Heap (JSFunction) | Przez referencję | (() =\u0026gt; {}) !== (() =\u0026gt; {}) | | Date | ❌ NIE | Heap (JSObject) | Przez referencję | new Date() !== new Date() | | RegExp | ❌ NIE | Heap (JSObject) | Przez referencję | /a/ !== /a/ | | Map/Set | ❌ NIE | Heap (JSObject) | Przez referencję | new Map() !== new Map() | Wniosek: Typy prymitywne to typy, których nie można zmienić (immutable).\nAle chwila\u0026hellip; co z toUpperCase()? Ktoś dociekliwy może zadać pytanie - skoro typy prymitywne są niezmienne, to jak to się ma do:\n\u0026#34;test\u0026#34;.toUpperCase() // \u0026#34;TEST\u0026#34; Możemy łatwo to zbadać używając makra %DebugPrint dostępnego w Node.js z flagą --allow-natives-syntax:\nconst str = \u0026#34;test\u0026#34;; %DebugPrint(str); // DebugPrint: 0x36c70031c8c5: [String] in ReadOnlySpace: #test // type: INTERNALIZED_ONE_BYTE_STRING_TYPE %DebugPrint(str.toUpperCase()); // DebugPrint: 0x36c70084d251: [String]: \u0026#34;TEST\u0026#34; // type: SEQ_ONE_BYTE_STRING_TYPE Zauważ różnicę w adresach:\n\u0026quot;test\u0026quot; → 0x36c70031c8c5 \u0026quot;TEST\u0026quot; → 0x36c70084d251 Wniosek: Wartość została skopiowana do nowego miejsca w pamięci - zasada immutability została zachowana!\nSemantyka vs Implementacja Skoro string \u0026quot;test\u0026quot; === \u0026quot;test\u0026quot; zwraca true, to jak to działa pod spodem?\nSpójrzmy na implementację porównania stringów w silniku SpiderMonkey (Firefox):\nbool js::EqualStrings(JSContext* cx, JSString* str1, JSString* str2, bool* result) { if (str1 == str2) { *result = true; return true; } // ... dalsza część funkcji porównuje zawartość gdy wskaźniki są różne return EqualStringsPure(str1, str2, result); } Powyższy kod pokazuje optymalizację (fast path):\n// Przypadek 1: Porównanie wskaźników - O(1) if (str1 == str2) { // jedna instrukcja CPU! return true; } // Przypadek 2: Porównanie zawartości - O(n) for (size_t i = 0; i \u0026lt; length; i++) { if (str1-\u0026gt;chars[i] != str2-\u0026gt;chars[i]) { return false; } } To pokazuje różnicę między semantyką języka a implementacją:\nPoziom String Object Semantyka Porównanie przez wartość Porównanie przez referencję Implementacja Optymalizacja: najpierw wskaźniki, potem zawartość Tylko wskaźniki String Interning (Atom Table) Dlaczego \u0026quot;test\u0026quot; === \u0026quot;test\u0026quot; działa szybko?\nSilniki JS stosują internalizację (interning) - literały stringowe są przechowywane w specjalnej tablicy (Atom Table w SpiderMonkey). Dzięki temu identyczne literały wskazują na ten sam obiekt w pamięci:\nStack: Heap (Atom Table): ┌──────────────────────┐ ┌──────────────────────┐ │ s1 → 0x26a432b2c140 │ ──────→│ 0x26a432b2c140: │ │ s2 → 0x26a432b2c140 │ ──────→│ JSString \u0026#34;test\u0026#34; │ └──────────────────────┘ │ length: 4 │ │ chars: \u0026#34;test\u0026#34; │ └──────────────────────┘ Uwaga: Nie wszystkie stringi są internalizowane! Dynamicznie tworzone stringi (np. \u0026quot;te\u0026quot; + \u0026quot;st\u0026quot;) mogą mieć różne adresy - wtedy silnik musi porównać zawartość O(n).\nWracając do obiektów\u0026hellip; Obiekty i tablice to skomplikowane struktury, które są mutable - w każdym momencie można je zmodyfikować.\nPrzy każdej definicji (zdawałoby się pustego) obiektu:\n{} === {} // false - ZAWSZE! silnik zawsze przydziela mu nowe miejsce w pamięci:\n{} // alokacja pod adresem 0x7ffff66f7090 {} // alokacja pod adresem 0x7ffff66f7098 (inny adres!) Kluczowy wniosek: Porównując obiekty/tablice, nie porównujemy ich wartości - tylko adres w pamięci, który nawet dla identycznie wyglądających obiektów będzie różny!\nCo mówi standard ECMA-262? Standard ECMA-262 definiuje to zachowanie w operacji IsStrictlyEqual (używanej przez operator ===):\n7.2.16 IsStrictlyEqual ( x, y ) 1. If Type(x) is not Type(y), return false. 2. If x is a Number, then a. Return Number::equal(x, y). 3. Return SameValueNonNumber(x, y). Gdzie SameValueNonNumber dla obiektów sprowadza się do:\n7.2.11 SameValueNonNumber ( x, y ) ... 6. NOTE: All other ECMAScript language values are compared by identity. 7. If x is y, return true; otherwise return false. Czyli dla obiektów sprawdzane jest, czy x i y to ten sam obiekt (ta sama referencja w pamięci), a nie czy mają taką samą zawartość.\nPodsumowanie Typ Porównanie === Dlaczego? Prymitywy Przez wartość Są immutable, mogą być internalizowane Obiekty Przez referencję Są mutable, każda definicja = nowa alokacja Dlatego {} !== {} - to dwa różne obiekty w pamięci, nawet jeśli wyglądają identycznie! :)\n","permalink":"https://pzarycki.pl/posts/object-is/","summary":"Dowiedz się, dlaczego obiekt nie jest równy obiektowi!","title":"Dlaczego {} !== {}"},{"content":"20 Października 2025 Mój smart home przestał być smart. Alexa milczała jak zaklęta. Ring doorbell pokazywał connecting\u0026hellip; w nieskończoność. Snapchat, Fortnite i inne działające serwisy zamieniające się w ekrany: Uwaga - Przerwa Techniczna.\nNie był to cyberatak. Nie był to hardware failure. To były trzy litery: DNS.\nCo się właściwie stało Schemat wydarzeń, który mamy z oficjalnych stron AWS, prezentował się następująco:\n23:49 PDT ─┐ │ Błąd rozwiązywania DNS dla endpointów DynamoDB ├─\u0026gt; Błędy API DynamoDB (US-EAST-1) │ 02:24 PDT ─┤ DNS NAPRAWIONY! 🎉 │ ...ale... │ ├─\u0026gt; System uruchamiania EC2 padł (zależność od DynamoDB) │ └─\u0026gt; RDS, ECS, Glue - wszystko co używa EC2 │ 09:38 PDT ─┤ Awaria health checków Network Load Balancera │ └─\u0026gt; Środowiska wykonawcze Lambda │ └─\u0026gt; Metryki CloudWatch │ └─\u0026gt; Totalny chaos połączeń │ 15:01 PDT ─┴─\u0026gt; PEŁNE PRZYWRÓCENIE (15h 12min później) Dotknięte serwisy: ├─ Gaming: Fortnite, Roblox, PlayStation Network ├─ Social: Snapchat, Signal ├─ Finanse: Coinbase, Robinhood, Venmo, banki US ├─ Amazon własne: Alexa, Ring, Prime Video ├─ Atlassian: Jira, Confluence └─ Infrastruktura: IAM, Zgłoszenia wsparcia, DynamoDB Global Tables DNS czyli 12 bajtów które zatrzymały pół internetu Czym właściwie jest DNS i dlaczego spowodował takie problemy i straty? `DNS to swego rodzaju książka telefoniczna internetu, dzięki której możemy w swobodny sposób się komunikować z internetem.\nZałóżmy, że nie posługujemy się DNS - chcemy coś wyszukać w Google. Zamiast google.com musimy wpisać adres IP, na przykład 142.250.179.142.\nCzy zamiast google.com byłbyś w stanie zapamiętać ciąg liczb? Pewnie nie! DNS to nasza książka telefoniczna, dzięki której możemy komunikować się za pomocą łatwych do zapamiętania adresów internetowych.\nJak to działa? Kod jest wart więcej niż 1000 słów. Posługując się językiem C i tym oto bardzo uproszczonym programem, zapytajmy serwer DNS Amazon, jaki IP ma DynamoDb! (lub możesz użyć gotowych programów takich jak host albo nslookup)\n#include \u0026lt;stdio.h\u0026gt; #include \u0026lt;string.h\u0026gt; #include \u0026lt;sys/socket.h\u0026gt; #include \u0026lt;netinet/in.h\u0026gt; #include \u0026lt;arpa/inet.h\u0026gt; int main() { int sock = socket(AF_INET, SOCK_DGRAM, 0); struct sockaddr_in dns = {AF_INET, htons(53), {inet_addr(\u0026#34;8.8.8.8\u0026#34;)}}; // To jest CAŁE pytanie DNS - tylko 12 bajtów nagłówka + nazwa domeny unsigned char q[] = { 0xAA,0xAA,0x01,0x00,0,1,0,0,0,0,0,0, // Header DNS 8,\u0026#39;d\u0026#39;,\u0026#39;y\u0026#39;,\u0026#39;n\u0026#39;,\u0026#39;a\u0026#39;,\u0026#39;m\u0026#39;,\u0026#39;o\u0026#39;,\u0026#39;d\u0026#39;,\u0026#39;b\u0026#39;, 9,\u0026#39;u\u0026#39;,\u0026#39;s\u0026#39;,\u0026#39;-\u0026#39;,\u0026#39;e\u0026#39;,\u0026#39;a\u0026#39;,\u0026#39;s\u0026#39;,\u0026#39;t\u0026#39;,\u0026#39;-\u0026#39;,\u0026#39;1\u0026#39;, 9,\u0026#39;a\u0026#39;,\u0026#39;m\u0026#39;,\u0026#39;a\u0026#39;,\u0026#39;z\u0026#39;,\u0026#39;o\u0026#39;,\u0026#39;n\u0026#39;,\u0026#39;a\u0026#39;,\u0026#39;w\u0026#39;,\u0026#39;s\u0026#39;, 3,\u0026#39;c\u0026#39;,\u0026#39;o\u0026#39;,\u0026#39;m\u0026#39;,0, 0,1,0,1 // Type A, Class IN }; unsigned char r[512]; sendto(sock, q, sizeof(q), 0, (struct sockaddr*)\u0026amp;dns, sizeof(dns)); int len = recvfrom(sock, r, 512, 0, NULL, NULL); // Odpowiedź DNS ma skomplikowaną strukturę, ale IP jest na końcu printf(\u0026#34;IP: %d.%d.%d.%d\\n\u0026#34;, r[len-4], r[len-3], r[len-2], r[len-1]); return 0; } A odpowiedź wygląda następująco:\nIP: 3.218.180.124 53 to port DNS. 8.8.8.8 to publicny serwer DNS Google (możesz użyć dowolnego serwera DNS). To wszystko, co potrzebne, żeby zapytać gdzie jest dynamodb.us-east-1.amazonaws.com.\nSerwery DNS używają protokołu UDP, który jest bardzo uproszczony w stosunku do TCP/IP i dzięki temu bardzo szybki. Oczywiście jak każdy protokół posiada standardyzację, która jest oznaczona numerem RFC 1035.\nSama awaria dotknęła DynamoDB - nierelacyjną bazę danych, która jest używana do przechowywania i przetwarzania dużych ilości danych, szczególnie tych o dużej skali i natężeniu ruchu.\nTakże ci, co używają i nie używają DynamoDB, byli dotknięci awarią, bo na przykład EC2 używa wewnętrznie DynamoDB do trzymania metadanych.\nDynamoDB jako architektura rozproszona DynamoDB, jak i inne usługi AWS, działają w architekturze rozproszonej i możemy to zobrazować następującym diagramem:\n🏗️ ARCHITEKTURA DynamoDB (rozproszona) LOAD BALANCER VIP (Virtual IP: 52.94.133.131) ↓ ┌───────────────────┼───────────────────┐ │ │ │ ┌───▼────┐ ┌───▼────┐ ┌───▼────┐ │ Node 1 │ │ Node 2 │ │ Node 3 │ │ AZ-1a │ │ AZ-1b │ │ AZ-1c │ │ IP │ │ IP │ │ IP │ └────────┘ └────────┘ └────────┘ ✅ ✅ ✅ DZIAŁA DZIAŁA DZIAŁA Skoro wszystko jest rozproszone, nawet serwery DNS, to co się wydarzyło? Cały system DNS dla us-east-1 miał problemy.\nJak się okazuje, \u0026ldquo;multi-region\u0026rdquo; wymaga prawdziwej niezależności, nie tylko replikacji danych.\nJak to się odnosi do naszego problemu DNS?\nWiele usług AWS, pomimo tego że były zlokalizowane w różnych miejscach, miały jeden punkt styku - w tym przypadku resolvowanie adresów DNS w us-east-1.\n// Cała infrastruktura zależna od jednego serwisu DynamoDB_IP = dns_resolve(\u0026#34;dynamodb.us-east-1.amazonaws.com\u0026#34;); ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Kiedy to failuje - wszystko failuje! Co to oznaczało w praktyce? Problem nie był w DynamoDB. Problem nie był w serwerach. Problem był w MAPIE.\nWszystkie usługi AWS pytały:\nGdzie jest dynamodb.us-east-1.amazonaws.com?\nI nikt nie potrafił odpowiedzieć. Serwery działały. Dane były bezpieczne. Ale NIKT nie wiedział, jak tam dotrzeć.\nCo to oznacza dla nas i jakie lekcje z tego możemy wyciągnąć ? chmura to nie magia tylko czyjeś serwery które mogą ulec awarii multiregion to nie to samo co nie zależność (zawsze coś może używać jednego miejsca o którym nie wiemy) testuj awarie szczególnie uwzględniając awarie sieciowe takie jak np DNS nie polegaj tylko na jednym providerze DNS ","permalink":"https://pzarycki.pl/posts/aws-dns-failure/","summary":"Co się stanie, gdy przestanie działać DNS? Praktyczna lekcja z Października 2025, kiedy AWS us-east-1 sparaliżowało tysiące aplikacji.","title":"Jak awaria DNS w AWS zatrzymała pół internetu na 15 godzin"},{"content":"Liczba, która zawsze zwraca to samo Dzisiaj przyjrzymy się typowi NaN, który w JavaScripcie jest określany typem number.\n\u0026gt; typeof NaN \u0026#39;number\u0026#39; W odpowiedzi dostajemy typ number.\nSkoro coś jest liczbą, to logika podpowiada nam, że możemy przeprowadzić na niej operacje matematyczne.\nA więc spróbujmy w takim razie coś do niej dodać lub zbadać jej wartość maksymalną lub minimalną.\n\u0026gt; NaN + 1 NaN \u0026gt; NaN - 1 NaN \u0026gt; Math.max(NaN) NaN \u0026gt; Math.min(NaN) NaN Jak widać, po dodaniu, odjęciu, zbadaniu wartości maksymalnej i minimalnej - zawsze otrzymujemy ten sam rezultat.\nSkoro tak jest, to po co nam taka wartość jest potrzebna?\nAby spróbować to wyjaśnić, zajrzyjmy do Firefox lub V8 w poszukiwaniu wykorzystania i implementacji NaN.\n// Firefox bool isNaN() const { return isDouble() \u0026amp;\u0026amp; std::isnan(toDouble()); } // V8 if (IsMinusZero(value)) return has_minus_zero(); if (std::isnan(value)) return has_nan(); Patrząc na kod przykładowych przeglądarek, do sprawdzania NaN jest wykorzystywana metoda z biblioteki standardowej std::isnan, co może już nam sugerować, że jest to coś, co pojawiło się niezależnie od JavaScriptu.\nI rzeczywiście, patrząc historycznie, pierwsza standardyzacja NaN pojawiła się w 1985 roku i został jej nadany numer IEEE 754.\nOd JavaScriptu do poziomu sprzętowego Uzbrojeni w tą wiedzę, napiszmy zatem prosty program w C, gdzie bazując na tym, co odnaleźliśmy w kodzie przeglądarek, sprawdzimy jak zachowuje się NaN.\n\u0026gt; NaN !== NaN true \u0026gt; 0 / 0 NaN #include \u0026lt;math.h\u0026gt; #include \u0026lt;stdint.h\u0026gt; #include \u0026lt;stdio.h\u0026gt; int main() { double x = 0.0 / 0.0; if (x != x) { printf(\u0026#34;NaN is not the same\\n\u0026#34;); } if (isnan(x)) { printf(\u0026#34;x is NaN\\n\u0026#34;); } uint64_t bits = *(uint64_t*)\u0026amp;x; printf(\u0026#34;NaN hex: 0x%016lx\\n\u0026#34;, bits); return 0; } Rezultat taki sam jak w przypadku JavaScriptu!\nNaN is not the same x is NaN NaN hex: 0xfff8000000000000 Już wiemy, że NaN spotykamy także w innych językach programowania\n#Python import math nan = float(\u0026#39;nan\u0026#39;) print(nan != nan) # True print(nan == nan) # False print(math.isnan(nan)) # True //C++ #include \u0026lt;iostream\u0026gt; #include \u0026lt;cmath\u0026gt; int main() { double nan = NAN; std::cout \u0026lt;\u0026lt; (nan != nan) \u0026lt;\u0026lt; std::endl; // 1 (true) std::cout \u0026lt;\u0026lt; (nan == nan) \u0026lt;\u0026lt; std::endl; // 0 (false) std::cout \u0026lt;\u0026lt; std::isnan(nan) \u0026lt;\u0026lt; std::endl; // 1 (true, proper way) return 0; } //Rust fn main() { let nan = f64::NAN; println!(\u0026#34;{}\u0026#34;, nan != nan); // true println!(\u0026#34;{}\u0026#34;, nan == nan); // false println!(\u0026#34;{}\u0026#34;, nan.is_nan()); // true (proper way) } \u0026hellip; ale dalej nie wiemy po co on jest.\nA skoro nie wiemy, to wygenerujmy kod assembly dla naszego skromnego programu (pomińmy sobie prolog i inicjowanie ramki stosu).\n# ===================================== # double x = 0.0 / 0.0; # ===================================== pxor\txmm0, xmm0 # xmm0 = 0.0 divsd\txmm0, xmm0 # xmm0 = 0.0 / 0.0 = NaN movsd\tQWORD PTR -8[rbp], xmm0 # x = NaN # ===================================== # if (x != x) { # ===================================== movsd\txmm0, QWORD PTR -8[rbp] # xmm0 = x ucomisd\txmm0, QWORD PTR -8[rbp] # compare x with x (sets PF=1 for NaN) jnp\t.L2 # skip if NOT NaN (PF=0) # NaN detected - kod tutaj .L2: # ===================================== # if (isnan(x)) { # ===================================== movsd\txmm0, QWORD PTR -8[rbp] # xmm0 = x ucomisd\txmm0, QWORD PTR -8[rbp] # compare x with x (sets PF=1 for NaN) jnp\t.L3 # skip if NOT NaN (PF=0) # NaN detected - kod tutaj .L3: Dla tych, co nie mieli styczności z assembly - dla nas warty uwagi jest rejestr xmm0, który wykonuje operacje na liczbach zmiennoprzecinkowych. Co jest logiczne: chcemy przeprowadzić operację na liczbach, CPU operuje na liczbach, także najszybciej będzie to przeprowadzić w rejestrach specjalnie do tego przeznaczonych!\nDo tego możemy zobaczyć instrukcję ucomisd, która jest odpowiedzialna za ustawianie flagi w przypadku, gdy wykryje NaN.\nJaki z tego płynie wniosek? NaN jest zaimplementowany na poziomie sprzętowym, a nie abstrakcji JavaScriptu.\nZatem - przepisując program na assembly, aby uniknąć niepotrzebnych abstrakcji, zbadajmy jego wynik działania:\n#include \u0026lt;stdio.h\u0026gt; #include \u0026lt;stdint.h\u0026gt; int main() { double x; uint64_t bits; __asm__ ( // double x = 0.0 / 0.0; \u0026#34;pxor xmm0, xmm0\\n\\t\u0026#34; // xmm0 = 0.0 \u0026#34;divsd xmm0, xmm0\\n\\t\u0026#34; // xmm0 = 0.0 / 0.0 = NaN // Save results \u0026#34;movsd %0, xmm0\\n\\t\u0026#34; // x = NaN \u0026#34;movq %1, xmm0\\n\\t\u0026#34; // bits = *(uint64_t*)\u0026amp;x : \u0026#34;=m\u0026#34; (x), \u0026#34;=r\u0026#34; (bits) : : \u0026#34;xmm0\u0026#34; ); int is_not_equal; __asm__ ( // if (x != x) \u0026#34;movsd xmm0, %1\\n\\t\u0026#34; // xmm0 = x \u0026#34;ucomisd xmm0, %1\\n\\t\u0026#34; // compare x with x → PF=1 for NaN \u0026#34;setp al\\n\\t\u0026#34; // al = (x != x) \u0026#34;movzx %0, al\\n\\t\u0026#34; // is_not_equal = al : \u0026#34;=r\u0026#34; (is_not_equal) : \u0026#34;m\u0026#34; (x) : \u0026#34;xmm0\u0026#34;, \u0026#34;al\u0026#34; ); if (is_not_equal) { // if (x != x) printf(\u0026#34;NaN is not the same\\n\u0026#34;); } int is_nan_result; __asm__ ( // if (isnan(x)) \u0026#34;movsd xmm0, %1\\n\\t\u0026#34; // xmm0 = x \u0026#34;ucomisd xmm0, %1\\n\\t\u0026#34; // compare x with x → PF=1 for NaN \u0026#34;setp al\\n\\t\u0026#34; // al = isnan(x) \u0026#34;movzx %0, al\\n\\t\u0026#34; // is_nan_result = al : \u0026#34;=r\u0026#34; (is_nan_result) : \u0026#34;m\u0026#34; (x) : \u0026#34;xmm0\u0026#34;, \u0026#34;al\u0026#34; ); if (is_nan_result) { // if (isnan(x)) printf(\u0026#34;x is NaN\\n\u0026#34;); } printf(\u0026#34;NaN hex: 0x%016lx\\n\u0026#34;, bits); return 0; } Rezultat?\nNaN is not the same x is NaN NaN hex: 0xfff8000000000000 Wynik programu jest taki sam jak w przypadku wysokopoziomowego C.\nWiemy już, że NaN jest natywnie zaimplementowany, także przyjrzyjmy się instrukcji ucomisd.\n\u0026#34;ucomisd xmm0, %1\\n\\t\u0026#34; // compare x with x → PF=1 for NaN ucomisd - czyli Unordered Compare Scalar Double-precision floating-point. Ta oto wspaniała instrukcja zaoszczędziła czas i nerwy programistom na architekturze x86, ponieważ już na etapie CPU sprawdza, czy wynik operacji na liczbach jest poprawny czy nie.\nNaN !== NaN Głównym powodem było zapewnienie programistom sposobu na wykrywanie NaN za pomocą testu x != x w czasach, gdy nie istniała jeszcze funkcja isnan() w językach programowania.\nZ punktu logicznego ma to dużo sensu, ponieważ nie-wartość nie może być równa nie-wartości.\nTo jest celowy design, a nie bug.\ntypeof NaN === \u0026ldquo;number\u0026rdquo; NaN jest częścią systemu liczbowego (IEEE 754), a nie osobnym typem. To specjalna wartość liczbowa sygnalizująca błąd operacji matematycznej.\nIEEE 754-1985: Standard for Binary Floating-Point Arithmetic Opublikowany: 1985 Autor: William Kahan (UC Berkeley) + komitet IEEE Definiuje: NaN, Infinity, denormalized numbers, rounding modes Kluczowe decyzje:\nNaN !== NaN (zawsze false przy porównaniu równości) Wykładnik = 0x7FF, mantysa ≠ 0 Quiet NaN (qNaN) - propaguje się przez operacje bez sygnalizowania wyjątku Signaling NaN (sNaN) - generuje wyjątek przy pierwszym użyciu w operacji Propagacja NaN (każda operacja z NaN → NaN) NaN jest liczbą, ale jaką? Może Cię zdziwiło, dlaczego rejestry dla liczb zmiennoprzecinkowych są używane w przypadku dzielenia 0/0.\nOperacje na liczbach typu number w JavaScripcie są reprezentowane jako liczby zmiennoprzecinkowe podwójnej precyzji (double), aby przeprowadzić na nich operacje zgodnie ze standardem IEEE 754.\nW operacjach na liczbach całkowitych dzielenie przez zero jest jednoznacznym błędem. W liczbach zmiennoprzecinkowych mamy jednak wiele przypadków, które mogą prowadzić do nieokreślonych wyników:\n0.0 / 0.0 → NaN ∞ - ∞ → NaN 0 * ∞ → NaN sqrt(-1) → NaN Bez standardu IEEE 754 każdy producent sprzętu radził sobie z tymi sytuacjami inaczej, co skutkowało ogromnymi problemami z przenośnością kodu.\n1994: Pentium FDIV bug Bug w dzieleniu zmiennoprzecinkowym Pentium - niektóre dzielenia dawały złe wyniki. Nie był to problem z NaN, ale pokazał wagę precyzyjnej implementacji IEEE 754.\nIntel wymienił miliony procesorów, co kosztowało firmę 475 milionów dolarów.\nNaN jako zbawca programistów Dowiedzieliśmy się, że NaN jest ustawiane na poziomie sprzętowym, ale co było zatem przed NaN?\nPrzed standardem IEEE 754 (1985) każdy producent sprzętu robił to po swojemu, co zazwyczaj oznaczało, że operacje takie jak 0/0 kończyły się crashem i zakończeniem programu.\nWymagało to od programistów bardzo defensywnego programowania. Wyobraź sobie, że lecisz samolotem, a w systemie sterowania programista nie przewidział 0/0 - instrukcja wykonuje się na CPU i crashuje cały program z powodu Division Error!\nIntel i pozostali producenci mieli już dosyć chaosu wynikającego z różnego działania programów na różnych architekturach.\nNaN (Not a Number) Możemy sobie zadawać pytania, czemu akurat specjalna wartość zamiast innego rozwiązania.\nRozważmy różne opcje:\nOpcja A: Division Error → CRASH (istniejąca przed IEEE 754)\nNieoczekiwane zakończenie programu (patrz przykład z samolotem) Wymaga defensywnego programowania przed każdą operacją Opcja B: Zwróć np. 0\nMatematycznie niepoprawne Maskuje błąd Dalsze obliczenia dają fałszywe wyniki Opcja C: Zwróć null lub specjalny kod błędu\nWymaga sprawdzania po każdej operacji Przerywa ciąg obliczeń matematycznych Typ wyniku staje się niespójny Opcja D: Specjalna wartość NaN (wybrane przez IEEE 754)\nWartość propaguje się przez obliczenia Program działa dalej Można sprawdzić wynik na końcu Zachowuje spójność typu (number) Co by było, gdyby nie było NaN? function divide(a, b) { // Check types if (typeof a !== \u0026#39;number\u0026#39; || typeof b !== \u0026#39;number\u0026#39;) { throw new Error(\u0026#39;Arguments must be numbers\u0026#39;); } // Check if numbers are valid if (!isFinite(a) || !isFinite(b)) { throw new Error(\u0026#39;Arguments must be finite\u0026#39;); } // Check divisor if (b === 0) { throw new Error(\u0026#39;Division by zero\u0026#39;); } return a / b; } function calculate(expression) { try { const result = divide(10, 0); return result; } catch (e) { console.error(e.message); return null; // Co zwrócić? null? undefined? 0? } } Co jest dzięki NaN? function divide(a, b) { return a / b; // Hardware robi resztę! } function calculate(expression) { return divide(10, 0); } const result = calculate(\u0026#34;10 / 0\u0026#34;); console.log(\u0026#34;Result:\u0026#34;, result); // Infinity const badResult = 0 / 0; if (Number.isNaN(badResult)) { console.log(\u0026#34;Invalid calculation\u0026#34;); } Podsumowanie NaN to eleganckie rozwiązanie problemu obsługi błędów w obliczeniach zmiennoprzecinkowych:\nZaimplementowane na poziomie sprzętowym (instrukcja ucomisd) Część standardu IEEE 754 od 1985 roku Propaguje się przez operacje, pozwalając wykryć błąd na końcu obliczeń NaN !== NaN to celowy design umożliwiający detekcję typeof NaN === \u0026quot;number\u0026quot; bo jest częścią systemu liczbowego, nie osobnym typem Źródła https://github.com/piotrzarycki/nan-from-scratch https://en.wikipedia.org/wiki/Pentium_FDIV_bug https://en.wikipedia.org/wiki/IEEE_754 https://csapp.cs.cmu.edu/public/waside/waside-sse.pdf ","permalink":"https://pzarycki.pl/posts/js-nan/","summary":"Dowiedz się, skąd się wziął NaN w JavaScripcie i czemu nie można go porównać","title":"Dlaczego NaN !== NaN w JavaScript (historia standardu IEEE 754)"},{"content":"Obiekt, który nie jest obiektem Język JavaScript ma swoje urokliwe zachowania, a jednym z nich jest operator typeof, który określa nam typ wartości.\nModelowe zachowanie:\ntypeof 100 //number typeof \u0026#34;string\u0026#34; //string typeof {} //object typeof Symbol //function typeof undefinedVariable // undefined I oto nasz faworyt, który jest dzisiejszym królem tego artykułu:\ntypeof null // object JavaScript, jak i inne języki programowania, posiada typy, które możemy podzielić na \u0026ldquo;prymitywne\u0026rdquo; - czyli te, które zwrócą jedną wartość (null, undefined, boolean, symbol, bigint, string) oraz typy \u0026ldquo;obiektowe\u0026rdquo;, które mają złożoną strukturę. Najprościej sprawę ujmując - przykładowo boolean w JavaScript jest czymś, co nie jest strukturą bardzo skomplikowaną, bo zwraca tylko jedną wartość: true albo false.\nPrzykładowo we współczesnej implementacji Firefox używana jest technika zwana \u0026ldquo;pointer tagging\u0026rdquo;, gdzie 64-bitowa wartość enkoduje typ i wartość lub adres na stercie (heap). Przyjrzyjmy się, jak są przykładowo obsługiwane booleany w tej implementacji:\nconst flagTrue = true; Keyword Tag Payload false JSVAL_TAG_BOOLEAN (0xFFFE*) 0x000000000000 true JSVAL_TAG_BOOLEAN (0xFFFE*) 0x000000000001 Można zauważyć, że wysokie bity odpowiedzialne są za definicję typu danych, a niskie za payload lub adres zaalokowanego obiektu na stercie (heap). Czyli w tym przypadku nasz true/false binarnie jest reprezentowany jako 1/0.\nPewnie się zastanawiasz, co to ma wspólnego z tym, że typeof null zwraca object zamiast null.\nAby to zrozumieć, musimy cofnąć się o 30 lat wstecz do oryginalnej implementacji JavaScript w Netscape, która używała 32-bitowego schematu tagowania - zupełnie innego niż współczesne silniki.\nBrendan Eich, który został zatrudniony w firmie Netscape, która w tamtym czasie była większościowym uczestnikiem rynku przeglądarkowego, z racji sporych wymagań rynkowych i depczącej po piętach konkurencji, takiej jak Microsoft czy Sun Microsystems, dostał zadanie stworzenia prototypu języka programowania, który miał spełniać kluczowe kryteria:\nbyć łatwym dla szerokiej grupy osób (bez statycznego typowania, instalowania kompilatorów) pozwalać użytkownikowi w podstawowym zakresie manipulować DOM-em Tak więc po 10 dniach powstał język programowania, który nosił nazwy: \u0026ldquo;Mocha\u0026rdquo;, \u0026ldquo;LiveScript\u0026rdquo;, a w końcu JavaScript ze względu na presję marketingową, aby trochę wykorzystać popularność w tamtym czasie Javy.\nPo 10 dniach powstał prototyp języka programowania, który pomimo faktu późniejszego upadku przeglądarki Netscape ze względu na konkurencję ze strony Microsoftu i domyślnej instalacji Internet Explorera na Windowsie - przetrwał do dzisiaj i się rozwinął.\nPrzeglądarka Netscape była napisana w języku C, jak i sama implementacja JavaScript. Tak więc przejdźmy do implementacji typeof w wersji przeglądarki Netscape Navigator 1.3, która witała ówczesnych programistów poleceniem help takim oto komunikatem:\njs\u0026gt; help() JavaScript-C 1.3 1998 06 30 A sam kod implementujący typeof tak:\nJS_TypeOfValue(JSContext *cx, jsval v) { JSType type; JSObject *obj; JSObjectOps *ops; JSClass *clasp; CHECK_REQUEST(cx); if (JSVAL_IS_VOID(v)) { type = JSTYPE_VOID; } else if (JSVAL_IS_OBJECT(v)) { obj = JSVAL_TO_OBJECT(v); if (obj \u0026amp;\u0026amp; (ops = obj-\u0026gt;map-\u0026gt;ops, ops == \u0026amp;js_ObjectOps ? (clasp = OBJ_GET_CLASS(cx, obj), clasp-\u0026gt;call || clasp == \u0026amp;js_FunctionClass) : ops-\u0026gt;call != 0)) { type = JSTYPE_FUNCTION; } else { type = JSTYPE_OBJECT; } } else if (JSVAL_IS_NUMBER(v)) { type = JSTYPE_NUMBER; } else if (JSVAL_IS_STRING(v)) { type = JSTYPE_STRING; } else if (JSVAL_IS_BOOLEAN(v)) { type = JSTYPE_BOOLEAN; } return type; } Makra definiujące typy danych w Netscape 1.3 wyglądały następująco:\n#define JSVAL_OBJECT 0x0 /* untagged reference to object */ #define JSVAL_INT 0x1 /* tagged 31-bit integer value */ #define JSVAL_DOUBLE 0x2 /* tagged reference to double */ #define JSVAL_STRING 0x4 /* tagged reference to string */ #define JSVAL_BOOLEAN 0x6 /* tagged boolean value */ Co też przekładało się na taką reprezentację w pamięci (system 32-bitowy):\nTyp Tag (Niskie 3 bity) Pamięć (32 bity) Wartość Object 000 (0x0) [29-bit pointer][000] 0x12345000 Integer 001 (0x1) [29-bit int value][001] 0x00006401 (42) Double 010 (0x2) [29-bit pointer][010] 0xABCDE002 → heap String 100 (0x4) [29-bit pointer][100] 0x78901004 → \u0026ldquo;hello\u0026rdquo; Boolean 110 (0x6) [29-bit value][110] 0x00000006 (true) Bazując na tych informacjach, możemy stworzyć uproszczony program przenosząc kilka makr z Netscape, aby zbadać ten problem (kod jest uproszczeniem dla celów edukacyjnych):\n#include \u0026lt;stdlib.h\u0026gt; #include \u0026lt;stdio.h\u0026gt; typedef unsigned long pruword; typedef long prword; typedef prword jsval; #define PR_BIT(n) ((pruword)1 \u0026lt;\u0026lt; (n)) #define PR_BITMASK(n) (PR_BIT(n) - 1) #define JSVAL_OBJECT 0x0 /* untagged reference to object */ #define OBJECT_TO_JSVAL(obj) ((jsval)(obj)) #define JSVAL_NULL OBJECT_TO_JSVAL(0) #define JSVAL_TAGMASK PR_BITMASK(JSVAL_TAGBITS) #define JSVAL_TAG(v) ((v) \u0026amp; JSVAL_TAGMASK) #define JSVAL_IS_OBJECT(v) (JSVAL_TAG(v) == JSVAL_OBJECT) #define JSVAL_TAGBITS 3 struct JSObject { struct JSObjectMap *map; }; struct JSObjectMap { }; // Funkcja pomocnicza do wyświetlania binarnej reprezentacji void print_binary(unsigned long n) { for (int i = 31; i \u0026gt;= 0; i--) { printf(\u0026#34;%d\u0026#34;, (n \u0026gt;\u0026gt; i) \u0026amp; 1); } printf(\u0026#34;\\n\u0026#34;); } int main() { struct JSObject* obj = malloc(sizeof(struct JSObject)); jsval objectValue = OBJECT_TO_JSVAL(obj); jsval null = JSVAL_NULL; printf(\u0026#34;Is object %d\\n\u0026#34;, JSVAL_IS_OBJECT(objectValue)); printf(\u0026#34;Is null an object %d\\n\u0026#34;, JSVAL_IS_OBJECT(null)); printf(\u0026#34;Binary representation of object: \u0026#34;); print_binary(objectValue); printf(\u0026#34;Binary representation of null: \u0026#34;); print_binary(null); } Wynik tego programu to:\nIs object 1 Is null an object 1 Binary representation of object: 01011000000010100011000111100000 Binary representation of null: 00000000000000000000000000000000 Jak widać, null i object zwracają to samo dla badanej wartości w makrze JSVAL_IS_OBJECT. Pewnie się zastanawiasz, dlaczego więc null i object są nierozróżnialne dla tego sprawdzenia.\nWyjaśnieniem tego jest powyższy model tagowania i bazowania na pamięci jako identyfikatorze typów obiektów w JavaScript. Skoro JavaScript jest językiem dynamicznie typowanym, w związku z tym deklaracje typów gdzieś musiały rezydować, więc w tym przypadku twórca podjął decyzję, aby wygospodarować 3 niskie bity na identyfikację typu.\nUstawienie 000 jako identyfikatora obiektu wynika z mechanizmu działania architektury 32-bitowej i wymagań sprzętowych związanych z wyrównaniem pamięci (memory alignment). Obiekty czy tablice to struktury, które są bardziej skomplikowane niż typy prymitywne, w związku z tym są alokowane na stercie.\nW architekturze 32-bitowej CPU ładuje dane w porcjach 32-bitowych (4 bajty), a system zarządzania pamięcią wymusza wyrównanie adresów obiektów do granic 4-bajtowych. To oznacza, że każdy adres wskaźnika do obiektu jest podzielny przez 4, co w reprezentacji binarnej skutkuje tym, że adresy obiektów zawsze kończą się dwoma zerami w zapisie bitowym (ponieważ 4 = 100 w systemie binarnym). W praktyce jednak użyto trzech najniższych bitów jako tagi, więc adresy miały wyrównanie do 8 bajtów, co gwarantowało trzy końcowe zera.\nW przypadku reprezentacji null możemy zobaczyć, że jest to wartość 0 (same zera), która odnosi się do null pointera w języku C, który w większości architektur jest definiowany jako ((void*)0), czyli nieistniejące miejsce w pamięci. Ponieważ null jest reprezentowany jako 0x00000000, a trzy najniższe bity to 000, makro JSVAL_IS_OBJECT uznaje null za obiekt!\nCzy była możliwość tego naprawienia? - oczywiście!\nJak możemy zauważyć, reprezentacja null to po prostu 0, czyli nieistniejące miejsce w pamięci, a obiekt to coś, co istnieje i o zgrozo losu makro, które prawidłowo sprawdzało null, było w kodzie, ale nie zostało użyte w funkcji typeof!\n#define JSVAL_IS_NULL(v) ((v) == JSVAL_NULL) A więc funkcja typeof powinna wyglądać tak:\nJS_TypeOfValue(JSContext *cx, jsval v) { JSType type; JSObject *obj; JSObjectOps *ops; JSClass *clasp; CHECK_REQUEST(cx); if (JSVAL_IS_NULL(v)) { //sprawdzamy czy value jest nullem! type = JSTYPE_NULL; } else if (JSVAL_IS_VOID(v)) { type = JSTYPE_VOID; } else if (JSVAL_IS_OBJECT(v)) { obj = JSVAL_TO_OBJECT(v); if (obj \u0026amp;\u0026amp; (ops = obj-\u0026gt;map-\u0026gt;ops, ops == \u0026amp;js_ObjectOps ? (clasp = OBJ_GET_CLASS(cx, obj), clasp-\u0026gt;call || clasp == \u0026amp;js_FunctionClass) : ops-\u0026gt;call != 0)) { type = JSTYPE_FUNCTION; } else { type = JSTYPE_OBJECT; } } else if (JSVAL_IS_NUMBER(v)) { type = JSTYPE_NUMBER; } else if (JSVAL_IS_STRING(v)) { type = JSTYPE_STRING; } else if (JSVAL_IS_BOOLEAN(v)) { type = JSTYPE_BOOLEAN; } return type; } Przykładową implementację z wyciętym kodem, który możesz sobie skompilować, znajdziesz tutaj:\nhttps://gist.github.com/piotrzarycki/a3713de4e63fd275216900a74c8521e2\nSkoro błąd był tak trywialny do naprawy, dlaczego nie zdecydowano się go naprawić?\nOtóż miliony stron już zaczęły używać JavaScript z tym oto błędem, miały jego świadomość i obsługiwały go w ten sposób.\nCo więcej, w 2013 roku była oficjalna propozycja naprawienia tego zachowania w standardzie ECMAScript, ale została odrzucona właśnie ze względu na kompatybilność wsteczną - zbyt wiele istniejącego kodu mogłoby przestać działać.\nDlatego też pomimo upływu 30 lat to zachowanie przypomina nam o kontekście powstawania JavaScript i historycznych decyzjach projektowych. Aby sprawdzić realnie, czy wartość jest obiektem, a nie nullem, musimy to obsługiwać np. w taki sposób:\nif (value !== null \u0026amp;\u0026amp; typeof value === \u0026#39;object\u0026#39;) { //to jest prawdziwy obiekt! } ","permalink":"https://pzarycki.pl/posts/js-null/","summary":"Dowiedz się, dlaczego w JavaScript typeof null zwraca \u0026lsquo;object\u0026rsquo; zamiast \u0026rsquo;null\u0026rsquo;","title":"Dlaczego typeof null === object"}]