TLDR
Wymuszenie typu lub niejawna konwersja typów umożliwia słabe pisanie i jest używane w całym JavaScript. Większość operatorów (z godnym uwagi wyjątkiem operatorów ścisłej równości ===i !==) oraz operacji sprawdzania wartości (np. if(value)...) Wymusi wartości dostarczone do nich, jeśli typy tych wartości nie są bezpośrednio zgodne z operacją.
Dokładny mechanizm używany do wymuszania wartości zależy od ocenianego wyrażenia. W pytaniu używany jest operator dodawania .
Operator dodawania najpierw upewni się, że oba operandy są prymitywami, co w tym przypadku obejmuje wywołanie valueOfmetody. toStringMetoda ta nazywa się w tym przypadku, ponieważ zastąpiona valueOfmetoda na obiekcie xzwraca pierwotną wartość.
Następnie, ponieważ jeden z argumentów w pytaniu jest łańcuchem, oba operandy są konwertowane na łańcuchy. Ten proces wykorzystuje abstrakcyjną, wewnętrzną operację ToString(uwaga: pisane wielką literą) i różni się od toStringmetody na obiekcie (lub łańcuchu prototypów).
Na koniec powstałe ciągi są konkatenowane.
Detale
W prototypie każdego obiektu funkcji konstruktora odpowiadającego każdemu typowi języka w JavaScript (tj. Number, BigInt, String, Boolean, Symbol i Object) istnieją dwie metody: valueOfi toString.
Celem valueOfjest pobranie wartości pierwotnej skojarzonej z obiektem (jeśli taki posiada). Jeśli obiekt nie ma podstawowej wartości pierwotnej, to obiekt jest po prostu zwracany.
Jeśli valueOfjest wywoływana w odniesieniu do prymitywu, wówczas prymityw jest w normalny sposób zamykany automatycznie i zwracana jest podstawowa wartość pierwotna. Zauważ, że w przypadku łańcuchów podstawową wartością pierwotną (tj. Wartością zwracaną przez valueOf) jest sama reprezentacja ciągu.
Poniższy kod pokazuje, że valueOfmetoda zwraca podstawową wartość pierwotną z obiektu otoki i pokazuje, jak niezmodyfikowane instancje obiektów, które nie odpowiadają elementom pierwotnym, nie mają wartości pierwotnej do zwrócenia, więc po prostu zwracają się same.
console.log(typeof new Boolean(true)) // 'object'
console.log(typeof new Boolean(true).valueOf()) // 'boolean'
console.log(({}).valueOf()) // {} (no primitive value to return)
Z toStringdrugiej strony, celem jest zwrócenie ciągu reprezentującego obiekt.
Na przykład:
console.log({}.toString()) // '[object Object]'
console.log(new Number(1).toString()) // '1'
W przypadku większości operacji JavaScript próbuje po cichu przekonwertować jeden lub więcej operandów na wymagany typ. To zachowanie zostało wybrane, aby ułatwić korzystanie z JavaScript. JavaScript początkowo nie miał wyjątków i mogło to również odegrać rolę w tej decyzji projektowej. Ten rodzaj niejawnej konwersji typu nazywany jest wymuszeniem typu i stanowi podstawę luźnego (słabego) systemu typu JavaScript. Złożone reguły stojące za tym zachowaniem mają na celu przeniesienie złożoności rzutowania typów do samego języka i poza kod.
Podczas procesu przymusu mogą wystąpić dwa tryby konwersji:
- Konwersja obiektu na element pierwotny (który może obejmować samą konwersję typów) i
- Bezpośrednia konwersja do instancji specyficzny typ, za pomocą obiektu Funkcja konstruktora jednego z typów pierwotnych (tj.
Number(), Boolean(), String()Itd.)
Nawrócenie na prymityw
Przy próbie konwersji typów innych niż pierwotne na prymitywy, na których mają być wykonywane operacje, ToPrimitivewywoływana jest operacja abstrakcyjna z opcjonalną wskazówką „liczba” lub „ciąg znaków”. Jeśli pominięto wskazówkę, domyślną wskazówką jest „liczba” (chyba że @@toPrimitivemetoda została nadpisana). Jeśli wskazówką jest „łańcuch”, toStringto najpierw jest próbowana, a valueOfdruga, jeśli toStringnie zwróciła prymitywu. W przeciwnym razie odwrotnie. Wskazówka zależy od operacji żądającej konwersji.
Operator dodawania nie dostarcza żadnej wskazówki, więc valueOfjest najpierw wypróbowywany. Operator odejmowania podaje wskazówkę dotyczącą „liczby”, więc valueOfjest próbowana jako pierwsza. Jedyne sytuacje, które mogę znaleźć w specyfikacji, w których wskazówką jest „ciąg znaków”, to:
Object#toString
- Operacja abstrakcyjna
ToPropertyKey, która konwertuje argument na wartość, która może być używana jako klucz właściwości
Bezpośrednia konwersja typu
Każdy operator ma własne zasady wykonywania operacji. Operator dodawania najpierw użyje, ToPrimitiveaby upewnić się, że każdy operand jest prymitywem; wtedy, jeśli którykolwiek z operandów jest łańcuchem, celowo wywoła operację abstrakcyjną ToStringna każdym operandzie, aby zapewnić zachowanie konkatenacji łańcuchów, którego oczekujemy z łańcuchami. Jeśli po ToPrimitivekroku oba operandy nie są łańcuchami, wykonywane jest dodawanie arytmetyczne.
W przeciwieństwie do dodawania, operator odejmowania nie ma przeciążonego zachowania, więc wywoła toNumerickażdy operand po uprzednim przekonwertowaniu go na prymitywy przy użyciu ToPrimitive.
Więc:
1 + 1 // 2
'1' + 1 // '11' Both already primitives, RHS converted to string, '1' + '1', '11'
1 + [2] // '12' [2].valueOf() returns an object, so `toString` fallback is used, 1 + String([2]), '1' + '2', 12
1 + {} // '1[object Object]' {}.valueOf() is not a primitive, so toString fallback used, String(1) + String({}), '1' + '[object Object]', '1[object Object]'
2 - {} // NaN {}.valueOf() is not a primitive, so toString fallback used => 2 - Number('[object Object]'), NaN
+'a' // NaN `ToPrimitive` passed 'number' hint), Number('a'), NaN
+'' // 0 `ToPrimitive` passed 'number' hint), Number(''), 0
+'-1' // -1 `ToPrimitive` passed 'number' hint), Number('-1'), -1
+{} // NaN `ToPrimitive` passed 'number' hint', `valueOf` returns an object, so falls back to `toString`, Number('[Object object]'), NaN
1 + 'a' // '1a' Both are primitives, one is a string, String(1) + 'a'
1 + {} // '1[object Object]' One primitive, one object, `ToPrimitive` passed no hint, meaning conversion to string will occur, one of the operands is now a string, String(1) + String({}), `1[object Object]`
[] + [] // '' Two objects, `ToPrimitive` passed no hint, String([]) + String([]), '' (empty string)
1 - 'a' // NaN Both are primitives, one is a string, `ToPrimitive` passed 'number' hint, 1-Number('a'), 1-NaN, NaN
1 - {} // NaN One primitive, one is an object, `ToPrimitive` passed 'number' hint, `valueOf` returns object, so falls back to `toString`, 1-Number([object Object]), 1-NaN, NaN
[] - [] // 0 Two objects, `ToPrimitive` passed 'number' hint => `valueOf` returns array instance, so falls back to `toString`, Number('')-Number(''), 0-0, 0
Należy zauważyć, że Dateobiekt wewnętrzny jest unikalny, ponieważ jest jedyną wewnętrzną @@toPrimitivemetodą zastępującą domyślną metodę, w której domyślną wskazówką jest „ciąg” (a nie „liczba”). Powodem tego jest to, że Dateinstancje są domyślnie tłumaczone na czytelne ciągi zamiast ich wartości liczbowych, dla wygody programisty. Możesz nadpisać @@toPrimitiveswoje własne obiekty za pomocą Symbol.toPrimitive.
Poniższa tabela przedstawia wyniki wymuszenia dla abstrakcyjnego operatora równości ( ==) ( źródło ):

Zobacz także .
window.console.log (x);lubalert (x);?