5 podstawowych koncepcji programowania dla programistów JavaScript
JavaScript jest słabo typowanym językiem programowania, co oznacza, że nie wymusza typów danych dla zmiennych. Tam, gdzie silnie typowane języki będą generować błędy w czasie kompilacji, gdy pewne zmienne są rzutowane na różne typy danych, JavaScript na ogół tego nie robi, a czasami nawet wykonuje rzutowanie typu w tle, co może prowadzić do błędów i nieprzewidzianych problemów. TypeScript rozwiązuje ten problem, ale dla tych, którzy rozwijają się w JavaScript, ten artykuł jest dobrym miejscem do zrozumienia, jak uniknąć pułapek słabo typowanego języka.
Omówimy pięć koncepcji: pierwszeństwo operatorów, asocjatywność, krótkie obwody, prymitywy i (niejawne) rzutowanie typów w JavaScript.
Wiele z tych koncepcji jest niezależnych od języka, jedyną zmianą byłoby to, że zasady mogą się różnić w zależności od języka programowania. Niezależnie od tego, czy jesteś doświadczonym programistą, czy dopiero zaczynasz, na pewno nauczysz się czegoś nowego!
Operatory, pierwszeństwo i asocjatywność
Operatory to zarezerwowane symbole w językach programowania używane do wykonywania obliczeń matematycznych, przypisań, porównań i operacji logicznych na zmiennych i wartościach (zwanych operandami). Są:
- operatory jednoargumentowe — działają na jednym argumencie (tj.
!operator negacji) - operatory binarne — działa na dwóch operandach (tj.
%operator modułu) - operatory trójskładnikowe — działa na trzech operandach (tj
?. operator trójskładnikowy) - operatory n -ary — działają na n argumentach (tj .
()operator wywołania funkcji)
Kolejność wykonania operatora
Pierwszeństwo odnosi się do kolejności wykonywania operatorów. Operator z większą wartością pierwszeństwa jest wykonywany przed operatorami o niższych wartościach pierwszeństwa. Bez pierwszeństwa wyrażenia takie jak 3 + 4 * 5mogą przynieść nieoczekiwane rezultaty i zamieszanie.
Poniżej znajduje się fragment tabeli JS Operator Precedence Mozilli.
Biorąc pod uwagę wyrażenie 3 + 4 * 5, ponieważ operator mnożenia ma wyższy priorytet niż operator dodawania, najpierw wykonuje się mnożenie z 3, a następnie dodaje się do tego wyniku, co daje końcowy wynik 23.
Więc zasadniczo 3+4*5staje się3 + (4*5)
Biorąc pod uwagę, że **ma pierwszeństwo 13, *ma pierwszeństwo 12 i +ma pierwszeństwo 11, czy możesz rozwiązać dla 3 + 4 ** 2 * 5?
Co jeśli pierwszeństwo jest takie samo?
Asocjatywność określa kolejność wykonywania operacji. Wiedza o tym jest najbardziej przydatna w przypadkach, gdy w wyrażeniu używanych jest wiele operacji o tym samym priorytecie. Na wcześniejszym obrazie widzieliśmy, że operatory +i -oba mają ten sam priorytet.
Rozważmy wyrażenie 1 + 2 - 3 + 4:
Jeśli operatory *i -mają asocjatywność od lewej do prawej, wówczas wyrażenie jest oceniane jako , (1 + 2) -3) + 4które daje 0 + 4 lub 4.
Jeśli operatory +i -mają asocjatywność od prawej do lewej, to wyrażenie 1 + (2 -(3 + 4)ma wartość 1 - 5 lub -4.
Lub rozważ wyrażenie 3 ** 3 ** 3, gdzie **jest potęgowanie:
Jeśli **ma asocjatywność od lewej do prawej, to mamy, (3**3)**3która wynosi 19683.
Ale przy asocjatywności od prawej do lewej mamy 3**(3**3)7625597484987.
Dlatego asocjatywność odgrywa kluczową rolę w określeniu, jakich wyników się spodziewać!
W JavaScript, podczas gdy większość operatorów arytmetycznych ma asocjatywność lewostronną, operator potęgowania jest asocjacyjny prawostronnie
Odnosząc się do powyższej tabeli, możemy teraz sprawdzić nasze wcześniejsze wyniki w Node
Operator przypisania to kolejny operator, który jest prawostronnie asocjacyjny
Stąd a=b=cprzekłada się na a=(b=c)i a=b+=cprzekłada się naa=(b+=c)
Chociaż pewne zasady mogą być podobne i obowiązywać w przypadku większości języków programowania, nie oznacza to, że wszystkie języki programowania mają te same zasady.
Ogólnie rzecz biorąc, zarówno pierwszeństwo, jak i łączność operatorów są określane przez reguły języka programowania, a ponieważ różne języki mogą mieć różne reguły, ważne jest, aby przeczytać dokumentację każdego języka w odniesieniu do działania ich operatorów.
W sytuacjach, w których operatory nie są asocjacyjne (nie dotyczy), zaleca się stosowanie nawiasów do grupowania wyrażeń.
Uwaga: odniosłem się tutaj do dokumentów MDN, ponieważ przewodnik po specyfikacji ECMAScript nie zawierał tabeli pierwszeństwa operatorów/powiązań
Chociaż wyrażenia o wyższym priorytecie są oceniane jako pierwsze, istnieją scenariusze, w których nie jest to prawdą. W przypadku zwarcia operand może w ogóle nie zostać oceniony!
Zwarcie (i operatory logiczne)
Niektóre operatory logiczne mają funkcję zwaną short-circuiting , w przypadku której interpreter przestaje oceniać wyrażenie, biorąc pod uwagę wynikową wartość operandu.
Jako przykład podano, x && (y || z)że jeśli xjest fałszywe (ocenia się na false), to (y || z)nigdy nie jest oceniane, mimo że ()ma wyższy priorytet niż &&.
Zwarcie jest zwykle stosowane, gdy pewne warunki w wyrażeniu są zależne od innych, więc nie zawsze konieczne jest obliczanie całego wyrażenia.
JS ma trzy binarne operatory logiczne, wszystkie z asocjatywnością od lewej do prawej
let user = { name : "George" }
const getUserName = (userObj) => {
if (user) {
if (user.name) {
console.log(user.name);
} else {
console.log("User object has no attribute called name");
}
} else {
console.log("User object does not exist");
}
};
const getUserNameWithShortCircuit = (userObj) => {
if (user && user.name) {
console.log(user.name);
} else {
console.log(`${user ? "User object has no attribute called name" : "User object does not exist"}`);
}
}
getUserName(user); // George
getUserNameWithShortCircuit(user); // George
user = {};
getUserName(user); // User object has no attribute called name
getUserNameWithShortCircuit(user); // User object has no attribute called name
user = null;
getUserName(user); // User object does not exist
getUserNameWithShortCircuit(user); // User object does not exist
Funkcja getUserNameWithShortCircuitużywa &&operatora logicznego (AND) i łączy oba warunki w jedno wyrażenie. Różnica polega na tym, że jeśli pierwszy operand ocenia, falseto drugie wyrażenie nie jest oceniane, a interpreter przeskakuje do elsebloku. Pomaga to skondensować logikę, a także pogrupować zależności między warunkami/operandami.
Innym przypadkiem użycia zwarć jest podanie wartości domyślnych
let userName = someNameVar || "default"
Uwaga : jeśli lewy operand jest fałszywy, ||zwraca drugi operand niezależnie od tego, czy jest to trueorfalse
let userName = "" || false; //returns false
console.log("" ?? false); // ""
console.log("" || false); // false
console.log(0 ?? false); // 0
console.log(0 || false); // false
console.log(null ?? false); // false
console.log(null || false); // false
console.log(undefined ?? false); // false
console.log(undefined || false); // false
console.log(true ?? false); // true
console.log(true || false); // true
Poniżej znajduje się dokumentacja dotycząca tych operatorów:
Zwarcie (więcej)
Odpowiedniki przypisania powyższych operatorów ( &&=, ||=, ??=) również zwierają się, tak że przypisanie w ogóle się nie dzieje.
Opcjonalny operator łańcuchowy powoduje również ?.zwarcie, jeśli lewym operandem jest nulllub undefined. Ponieważ nie jest to operator logiczny, ale używany do uzyskiwania dostępu do właściwości obiektu, nie zagłębiałem się w to, ale pomyślałem, że warto o tym wspomnieć.
Teraz, gdy omówiliśmy operatory, czas poznać ich operandy!
prymitywy
Prymitywy są uważane za elementy składowe większości języków programowania. W JavaScript są to typy danych, które nie są obiektami; stąd nie mają metod ani właściwości. Prymitywy są również niezmienne i dlatego ich wartość nie może zostać zmieniona.
JavaScript ma siedem pierwotnych typów danych:
- nieokreślony
- zero
- numer
- bigint
- symbol
- strunowy
- logiczna
nieokreślony
Zmienna jest undefined, jeśli jej wartość lub właściwość nie została zadeklarowana ani przypisana.
Zmienna ajest typu undefined, gdy jest inicjowana bez wartości. Również jeśli ajest pustym obiektem i próbujemy uzyskać dostęp do właściwości, która nie istnieje, zamiast błędu otrzymujemy, że właściwość jestundefined
Wręcz przeciwnie, przy próbie uzyskania dostępu do zmiennej, która nie istnieje/nie została zadeklarowana, wyrzuca to aReferenceError
Funkcje, które nie zwracają żadnej wartości, nie zwracają wartościundefined
Dostęp do indeksu tablicy, który nie istnieje, również zwracaundefined
zero
Inaczej niż undefinedktóry oznacza zmienną pozbawioną jakiejkolwiek wartości, nulljest wartością przypisaną zmiennej i ma reprezentować „brak wartości”. Dlatego nulljest postrzegany jako wartość obiektu i chociaż typeof nullma wartość object, nie jest obiektem, ponieważ nie ma właściwości ani metod i nie jest zmienny.
let newObject;
console.log(newObject); // undefined
newObject = null;
console.log(newObject); // null
console.log(newObject === null); // null
newObject = { name: "John Done", age: 30, email: "[email protected]" }
//Clear object's value by assigning null
newObject = null;
Ponieważ typeof nullocenia się na object, aby sprawdzić, czy obiekt jest pusty, zaleca się użycie operatora === ścisłej równości . Wszystkie inne prymitywy można przetestować za pomocą typeofoperatora.
symbol
Typ symboldanych to nowsza funkcja JS, która pojawiła się wraz z ES6. Gwarantuje się, że symbole będą unikalne, a ponieważ są niezmienne, świetnie nadają się do wykorzystania jako pliki constants.
Symbole są tworzone za pomocą wbudowanego Symbolobiektu, którego konstruktor przyjmuje jeden opcjonalny argument (który ustawia descriptionatrybut).
const firstName = Symbol("First name");
const lastName = Symbol("Last name");
let person = {
[firstName]: "George",
[lastName]: "Anton",
age: "Forever Young"
};
console.log(person[firstName]); // "George"
console.log(person[lastName]); // "Anton"
const new_symbol = Symbol("This is a new symbol");
const new_symbol2 = Symbol();
console.log(new_symbol.description) // "This is a new symbol"
console.log(new_symbol2.description) // undefined
Atrybut opisu występuje undefined, jeśli nie został podany.
const firstName = Symbol.for("First name");
const lastName = Symbol.for("Last name");
let person = {
[firstName]: "George",
[lastName]: "Anton",
age: "Forever Young"
};
console.log(person[firstName]); // "George"
console.log(person[lastName]); // "Anton"
Jak pokazano poniżej, symboli nie można uzyskać za pomocą Member Accessoperatora (kropki) ani ujawnić metodami Object.getOwnPropertyNamesi Object.keys.
Nie można ich również przekonwertować, stringwięc JSON.stringifymetoda je pomija (i nie można ich również przekonwertować na numbertyp).
Chociaż nie oznacza to, że symbole są prywatne.
Aby uzyskać dostęp do ich wartości, użyj Computed Member AccesslubReflect.ownKeys
Typowym przypadkiem użycia symbolssą jako klucze do objectwłaściwości, ponieważ ich wyjątkowość pozwala uniknąć kolizji nazw. Przed symbolami musieliśmy używać liczb lub bibliotek, takich jak uuidtworzenie unikalnych ciągów znaków dla kluczy obiektowych, co nie zawsze jest idealne.
numer
Typ numberdanych jest domyślnym typem wartości liczbowych w JavaScript. Obejmuje liczby całkowite, wartości zmiennoprzecinkowe i wartości specjalne, takie jak Infinity, -Infinity, NaN(co oznacza „Not a Number”) i oba +/- 0. Ponieważ Liczby są reprezentowane za pomocą float o podwójnej precyzji , oznacza to, że mają ograniczoną precyzję, a zatem mają skończoną reprezentację.
Wartości całkowite są ograniczone w przedziale [-2⁵³, 2⁵³]
Number.MIN_SAFE_INT, Number.MAX_SAFE_INTEGERzwróć maksymalne/minimalne wartości int
Maksymalne/minimalne wartości zmiennoprzecinkowe można znaleźć za pomocą atrybutów Number.MAX_VALUEi Number.MIN_VALUE, wartości poza tym zakresem nie gwarantują precyzji.
Pływaki spoza zakresu [-2¹⁰²⁴,2¹⁰²⁴] są reprezentowane przez+/- Infinity
Według ECMAScript istnieje 2⁵³-2 różnych NaNwartości, z których każda jest nie do odróżnienia od drugiej (jest to jedyna wartość w JavaScript, która nie jest sobie równa).
NaNwartości pojawiają się podczas wykonywania operacji arytmetycznych na wartości nieliczbowej lub podczas używania Numberdo konwersji wartości/łańcuchów znaków nienumerycznych
bigint
bigintjest najnowszym prymitywem wprowadzonym do JavaScript i pozwala nam reprezentować liczby całkowite dowolnej wielkości bez utraty precyzji. Dzięki zastosowaniu arytmetyki o dowolnej precyzji zdolność biginta do reprezentowania cyfry jest ograniczona jedynie dostępną pamięcią systemu hosta.
Oznaczanie a bigintodbywa się poprzez dołączenie nna końcu literału całkowitego
Dzienniki w wierszach 7 i 10 wskazują, że numberliterały załamują się podczas wykonywania operacji arytmetycznych dla, (2⁵³-1) + 2podczas gdy bigintnie
Rzutowanie wartości liczbowej na bigintmożna również wykonać za pomocą funkcji globalnej,BigInt()
Tam , gdzie numberużywają literałów Infinity, BigIntjest w stanie przetworzyć wartość
Operacje między typami biginti numberrzucą aTypeError
Zostanie TypeErrorwyrzucony, aby uniknąć utraty precyzji podczas obliczeń. Zatem niejawne rzutowanie wykonywane przez operatorów na różne typy nie będzie działać dla BigInt. Jako najlepszą praktykę zaleca się, aby w programie pracować wyłącznie z jednym bigintlub numberdwoma typami, a nie z obydwoma.
Utrata precyzji może wystąpić podczas rzutowania wartości większych niż Number.MAX_SAFE_INTEGERprzy użyciu BigIntfunkcji, jak pokazano powyżej z numberkonwersją typu. Zaleca się rzutowanie większych wartości, gdy są one stringtypu, aby uniknąć tego zachowania.
BigIntumożliwia wykonywanie operacji na liczbach całkowitych bez ryzyka przepełnienia i utraty precyzji, a docelowo może stać się podstawą BigDecimalimplementacji w JavaScript!
Widzieliśmy to podczas wykonywania operacji arytmetycznych na typach biginti number, interpreter rzuca TypeError. Chociaż często zdarza się, że operatorzy wykonują obliczenia na różnych prymitywach (praktycznie w ten sposób otrzymujemy NaNwartości!). Takie obliczenia mogą prowadzić do nieoczekiwanych wyników, a nawet błędów, a to dzięki czemuś znanemu jako niejawne rzutowanie typów (lub wymuszanie).
Niejawne rzutowanie typów (przymus)
Rzutowanie typu to proces konwersji wartości z jednego typu danych na inny. Istnieją 2 sposoby wykonywania rzutowania typów: niejawnie i jawnie.
Kiedy użyliśmy BigInt()funkcji globalnej do konwersji stringwartości na bigint, jest to forma jawnego rzutowania typów. Number()i String()są Boolean()trzy inne funkcje dostępne globalnie do rzutowania jawnego.
Z drugiej strony, niejawne rzutowanie typów ma miejsce, gdy typy danych są konwertowane przez sam język, bez Twojej wiedzy. Jedynym sposobem, aby to wiedzieć, jest uświadomienie sobie reguł, które język stosuje, aby przejść te rzuty.
Sidenote : BigInttyp nie ma niejawnych konwersji w JavaScript.
Powyżej widzimy przykład niejawnego rzutowania w akcji. Operator +jest wyposażony zarówno w łączenie łańcuchów, jak i dodawanie numeryczne. Chociaż gdy operandy są różnych typów, zamiast wyrzucać TypeError, wydaje się, że JavaScript niejawnie konwertuje numbertyp na, stringa następnie zwraca wynik konkatenacji łańcuchów.
Stąd zamiast uzyskać oczekiwaną wartość 3otrzymujemy 12.
Przejdźmy do oceny:
- Kroki 2 i 4 pobierają wartość wyrażenia, w tym przypadku nasze operandy
xiy - Kroki 5 i 6 są ignorowane, ponieważ
ToPrimitive()działają na obiektach, a wartości naszych operandów są typu pierwotnegonumberistring - Krok 7 sprawdza warunek, czy którakolwiek z wartości jest typu
string. W tym przypadkuyis, a zatem7akonwertuje naszą pierwotną wartość forxnastring„2” i7czwraca nas"1" + "2", czyli"12".
Czego powinniśmy się teraz spodziewać, jeśli xjest a boolean, nulllub undefined?
Jak widać, we wszystkich powyższych przypadkach xjest konwertowany na stringtyp i łączony z y. Dzieje się tak po prostu dlatego , że yjest typu string(krok 7 )
Co jeśli ynie jest typem string?
Przyjrzyjmy się ponownie ocenie:
- Kroki 2 i 4 pobierają wartość dla każdego operandu
- Kroki 5 i 6 są ignorowane, ponieważ we wszystkich przypadkach mamy do czynienia z prymitywami
- Krok 7 jest ignorowany, ponieważ żaden
xlub nieyjest typemstring - Kroki 8 i 9 konwertują wartości
xiynanumber - Krok 10 dodaje sprawdzenie, aby upewnić się, że oba
numbertypy są takie same i że poprzedniaToNumeric()konwersja zakończyła się pomyślnie - Krok 12 zwraca wynik
Number::add, który jest numeryczną arytmetyką na rzutowanychnumberwartościach.
- Ponieważ
Number(undefined)isNaNitypeof NaNisnumber, wartość dwóch niezdefiniowanych zmiennychNaNjest oceniana zgodnie z oczekiwaniami.
3. nullprzekonwertowany na liczbę to 0, którą można sprawdzić za pomocą Number(null), stąd 0 + 0 = 0.
4/5. booleantrue i false są rzutowane odpowiednio na 1i 0, dając oczekiwane wyniki.
Dlatego wiedza o tym, jak operatorzy traktują różne typy danych, pomaga zapobiegać błędom. Niejawne rzutowanie nie jest unikalne dla +operatora!
Teraz jeden przypadek, który pominęliśmy, brzmi: jak sobie z tym +poradzić objects?
ToPrimitive() i prymitywny przymus
Jeśli pamiętamy, kroki 5 i 6 wywołują ToPrimitive()wartości operandów.
W JavaScript operacje abstrakcyjne to specyfikacje algorytmu lub zachowania, które są używane wewnętrznie przez język, aby pomóc w wykonywaniu określonych zadań, takich jak przymus.
ToPrimitive()jest operacją abstrakcyjną i nie jest dostępna dla programistów.
ToPrimitivesłuży do konwersji obiektów na prymitywy i przyjmuje 2 argumenty.
Jej pierwszy argument, input, to wyrażenie lub objectdo konwersji, a drugi, opcjonalny preferredType, to jeden z „default”, „string” lub „number”. Jest preferredTyperównież określany jako wskazówka .
Krok 1 sprawdza, czy dane wejściowe są typu object, jeśli nie, to dane wejściowe są już prymitywne i nie jest wykonywana żadna akcja (krok 2 ).
Operacja ToPrimitivedziała w następującej kolejności:
- Sprawdza
valueOfmetodę obiektu i jeśli istnieje, wywołuje ją. JeślivalueOfzwraca wartość pierwotną, używana jest ta wartość podstawowa. - Jeśli
valueOfnie zwraca prymitywu, to patrzy natoStringmetodę obiektu i jeśli istnieje, wywołuje ją. JeślitoStringzwraca prymityw, to ta wartość jest używana. - Jeśli
toStringnie zwróci prymitywu, patrzymy naObject.prototypewbudowane metody dlavalueOfitoString(powtarzając kroki 1 i 2, aż do zwrócenia prymitywu). - Jeśli nie zostanie zwrócony żaden prymityw,
TypeErrorrzucane jest a.
Przypomnijmy sobie teraz, że jest to krok 5 castingu operatora . +Dlatego prymityw 2zostaje przekonwertowany na string, jeśli drugim operandem jest łańcuch, a zatem mamy konkatenację łańcuchów. W przeciwnym razie pozostaje a numberi dodajemy.
Jednym ze sposobów zastąpienia poprzedniego ToPrimitivezachowania operacji jest podanie podpowiedzi. Poprzednio wskazówka była ustawiona jako „domyślna” i aby podać podpowiedź, musimy użyć jawnego rzutowania.
Kiedy jawnie rzucamy „obj” za pomocą String, mówimy, ToPrimitiveże chcemy użyć toString()najpierw (to jest nasze preferredType) iw obu sytuacjach Hellojest zwracane (i wtedy wracamy do kroku 7 +instrukcji).
Kroki, które właśnie opisaliśmy, są w rzeczywistości zdefiniowane przez OrdinaryToPrimitiveoperację abstrakcyjną, którą jest w rzeczywistości krok 1d w ToPrimitive. Zasadniczo pominęliśmy 1a — 1c. Teraz omówię, kiedy stosuje się 1a i co exoticToPrimoznacza.
Wewnętrzna metoda [@@toPrimitive]().
Widzieliśmy, że przekazując wskazówkę do ToPrimitiveoperacji abstrakcyjnej, na przykład stringwewnętrznie wywołujemy odpowiednie metody (w tym przypadku toString()) obiektu (jeśli takie metody istnieją).
(Wewnętrzną) funkcją, która wykonuje te zadania, jest dobrze znany symbol o nazwie [@@toPrimitive]()(„dobrze znane ” symbole są oznaczane przez @@i są predefiniowanymi symbolami w JavaScript).
Chociaż ta symbolmetoda jest wewnętrzna w JavaScript, można ją zastąpić za pomocą Symbol.toPrimitive. JavaScript wyświetla obiekty z tą metodą jako egzotyczne , a exoticToPrimwłaściwość zostaje zdefiniowana (Kroki 1a/b ).
Powyżej znajduje się przykład egzotycznego obiektu. Chociaż valueOfi toStringsą zdefiniowane, [Symbol.toPrimitive]()metoda zastępuje te zachowania.
Ponieważ nie mamy wyraźnego hint === "default"lub po prostu returnpoza zakresem if/else-ifbloku, wtedy obj + " World"zwrócimy undefined. Dzieje się tak, ponieważ zastąpiliśmy zachowanie „domyślne” i [@@toPrimitive]()oczekujemy wartości z podpowiedzi dla „domyślnej”, ale nie mamy żadnej przypisanej.
Podsumowanie
Podsumowując, przyjrzyjmy się, dlaczego {} + []zwraca [object Object]:
- Kroki 2, 4 operacji
+iGetValue({}) = {}GetValue([]) = [] - Ponieważ nie są one prymitywne, krok 5, 6 wywołania
+operacjiToPrimitive - Krok 1 operacji
ToPrimitivepotwierdza{}i[]jestobjecttypem. - Pomijamy krok 1a , 1b , ponieważ są one puste i dlatego nie są egzotyczne
- Krok 1c wykorzystuje
OrdinaryToPrimitive. Ponieważ są one puste, nie ma metod zdefiniowanych przez użytkownikavalueOfi .toString - Najpierw rozważ lewy operand.
{}jest członkiem,Object.prototypea zatem dziedziczy zarówno a , jak.valueOf()i.toString()metodę. DomyślnieOrdinaryToPrimitivewywołania ,{}.valueOf()które zwracająobject. - Ponieważ
objectnie jest prymitywem, następnie wywołuje{}.toString(), to zwracastringtyp[object Object]. - Rozważ prawy operand.
[]jest członkiem,Array.prototypea także dziedziczy zarówno a , jak.valueOf()i.toString()metodę. Pierwsza[].valueOf()jest wywoływana, która zwracaobjecttyp - Ponieważ
objectnie jest prymitywem, następnie wywołuje{}.toString(), to zwracastringtyp"”. - Krok 7 operacji
+, ponieważ oba operandystring, wykonujemy konkatenację łańcuchów, a więc[object Object] + ""ocenia do łańcucha[object Object].
Jeśli dotarłeś tak daleko, chciałbym podzielić się inspiracją do tego artykułu:
Sprawdź, czy możesz zrozumieć wyniki, biorąc pod uwagę to, co teraz wiemy!
Mam nadzieję, że ten artykuł zawiera informacje, a przede wszystkim dziękuję za przeczytanie!
Connect with me on LinkedIn.
- Przewodnik ECMAScript
- Operatory — przewodnik po ECMAScript
- Konwersje typów — przewodnik po ECMAScript
- Pierwszeństwo operatorów JS — Dokumenty MDN
- Struktury danych JavaScript — MDN Docs
- BigInt Primitive — Dokumentacja V8

![Czym w ogóle jest lista połączona? [Część 1]](https://post.nghiatu.com/assets/images/m/max/724/1*Xokk6XOjWyIGCBujkJsCzQ.jpeg)



































