You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
@@ -571,7 +571,96 @@ Nevertheless, as I mentioned at the start of this chapter, Brendan Eich endorses
571
571
572
572
### To Number
573
573
574
-
// TODO
574
+
Numeric coercions are a bit more complicated than string coercions, since we can be talking about either `number` or `bigint` as the target type. There's also a much smaller set of values that can be validly represented numerically (everything else becomes `NaN`).
575
+
576
+
Let's start with the `Number(..)` and `BigInt(..)` functions (no `new` keywords):
577
+
578
+
```js
579
+
Number("42"); // 42
580
+
Number("-3.141596"); // -3.141596
581
+
Number("-0"); // -0
582
+
583
+
BigInt("42"); // 42n
584
+
BigInt("-0"); // 0n
585
+
```
586
+
587
+
`Number` coercion which fails (not recognized) results in `NaN` (see "Invalid Number" in Chapter 1), whereas `BigInt` throws an exception:
588
+
589
+
```js
590
+
Number("123px"); // NaN
591
+
592
+
BigInt("123px");
593
+
// SyntaxError: Cannot convert 123px to a BigInt
594
+
```
595
+
596
+
Moreover, even though `42n` is valid syntax as a literal `bigint`, the string `"42n"` is never a recognized string representation of a `bigint`, by either of the coercive function forms:
597
+
598
+
```js
599
+
Number("42n"); // NaN
600
+
601
+
BigInt("42n");
602
+
// SyntaxError: Cannot convert 42n to a BigInt
603
+
```
604
+
605
+
However, we *can* coerce numeric strings with other representations of the numbers than typical base-10 decimals (see Chapter 1 for more information):
606
+
607
+
```js
608
+
Number("0b101010"); // 42
609
+
610
+
BigInt("0b101010"); // 42n
611
+
```
612
+
613
+
Typically, `Number(..)` and `BigInt(..)` receive string values, but that's not actually required. For example, `true` and `false` coerce to their typical numeric equivalents:
614
+
615
+
```js
616
+
Number(true); // 1
617
+
Number(false); // 0
618
+
619
+
BigInt(true); // 1n
620
+
BigInt(false); // 0n
621
+
```
622
+
623
+
You can also generally coerce between `number` and `bigint` types:
624
+
625
+
```js
626
+
Number(42n); // 42
627
+
Number(42n**1000n); // Infinity
628
+
629
+
BigInt(42); // 42n
630
+
```
631
+
632
+
We can also use the `+` unary operator, which is commonly assumed to coerce the same as the `Number(..)` function:
633
+
634
+
```js
635
+
+"42"; // 42
636
+
+"0b101010"; // 42
637
+
```
638
+
639
+
Be careful though. If the coercions are unsafe/invalid in certain ways, exceptions are thrown:
640
+
641
+
```js
642
+
BigInt(3.141596);
643
+
// RangeError: The number 3.141596 cannot be converted to a BigInt
644
+
645
+
+42n;
646
+
// TypeError: Cannot convert a BigInt value to a number
647
+
```
648
+
649
+
Clearly, `3.141596` does not safely coerce to an integer, let alone a `bigint`.
650
+
651
+
But `+42n` throwing an exception is an interesting case. By contrast, `Number(42n)` works fine, so it's a bit surprising that `+42n` fails.
652
+
653
+
| WARNING: |
654
+
| :--- |
655
+
| That surprise is especially palpable since prepending a `+` in front of a number is typically assumed to just mean a "positive number", the same way `-` in front a number is assumed to mean a "negative number". As explained in Chapter 1, however, JS numeric syntax (`number` and `bigint`) recognize no syntax for "negative values". All numeric literals are parsed as "positive" by default. If a `+` or `-` is prepended, those are treated as unary operators applied against the parsed (positive) number. |
656
+
657
+
OK, so `+42n` is parsed as `+(42n)`. But still... why is `+` throwing an exception here?
658
+
659
+
You might recall earlier when we showed that JS allows *explicit* string coercion of symbol values, but disallows *implicit* string coercions? The same thing is going on here. JS language design interprets unary `+` in front of a `bigint` value as an *implicit*`ToNumber()` coercion (thus disallowed!), but `Number(..)` is interpreted as an *explicit*`ToNumber()` coercion (thus allowed!).
660
+
661
+
In other words, contrary to popular assumption/assertion, `Number(..)` and `+` are not interchangable. I think `Number(..)` is the safer/more reliable form.
662
+
663
+
Like string coercions, if you perform a numeric coercion on a non-primitive object value, the `ToPrimitive()` operation is activated to first turn it into some primitive value
575
664
576
665
### To Primitive
577
666
@@ -674,7 +763,38 @@ Again, as we saw in the "To Number" section, `42` can safely be coerced to `42n`
674
763
675
764
We've seen that `toString()` and `valueOf()` are invoked, variously, as certain `string` and `number` / `bigint` coercions are performed.
676
765
677
-
What about `boolean` coercions?
766
+
#### No Primitive Found?
767
+
768
+
If `ToPrimitive()` fails to produce a primitive value, an exception will be thrown:
769
+
770
+
```js
771
+
spyObject4 = {
772
+
toString() {
773
+
console.log("toString() invoked!");
774
+
return [];
775
+
},
776
+
valueOf() {
777
+
console.log("valueOf() invoked!");
778
+
return {};
779
+
}
780
+
};
781
+
782
+
String(spyObject4);
783
+
// toString() invoked!
784
+
// valueOf() invoked!
785
+
// TypeError: Cannot convert object to primitive value
786
+
787
+
Number(spyObject4);
788
+
// valueOf() invoked!
789
+
// toString() invoked!
790
+
// TypeError: Cannot convert object to primitive value
791
+
```
792
+
793
+
If you're going to define custom to-primitive coercions via `toString()` / `valueOf()`, make sure to return a primitive from at least one of them!
794
+
795
+
#### Object To Boolean
796
+
797
+
What about `boolean` coercions of objects?
678
798
679
799
```js
680
800
Boolean(spyObject);
@@ -700,7 +820,7 @@ while (spyObject) {
700
820
701
821
Each of these are activating `ToBoolean()`. But if you recall from earlier, *that* algorithm never delegates to `ToPrimitive()`; thus, we don't see "valueOf() invoked!" being logged out.
702
822
703
-
#### Unboxing
823
+
#### Unboxing: Wrapper To Primitive
704
824
705
825
A special form of objects that are often `ToPrimitive()` coerced: boxed/wrapped primitives (as seen in Chapter 3). This particular object-to-primitive coercion is often referred to as *unboxing*.
706
826
@@ -737,42 +857,42 @@ Remember, this is because `ToBoolean()` does *not* reduce an object to its primi
737
857
As we've seen, you can always define a `toString()` on an object to have *it* invoked by the appropriate `ToPrimitive()` coercion. But another option is to override the `Symbol.toStringTag`:
738
858
739
859
```js
740
-
spyObject4a= {};
741
-
String(spyObject4a);
860
+
spyObject5a= {};
861
+
String(spyObject5a);
742
862
// "[object Object]"
743
-
spyObject4a.toString();
863
+
spyObject5a.toString();
744
864
// "[object Object]"
745
865
746
-
spyObject4b= {
866
+
spyObject5b= {
747
867
[Symbol.toStringTag]:"my-spy-object"
748
868
};
749
-
String(spyObject4b);
869
+
String(spyObject5b);
750
870
// "[object my-spy-object]"
751
-
spyObject4b.toString();
871
+
spyObject5b.toString();
752
872
// "[object my-spy-object]"
753
873
754
-
spyObject4c= {
874
+
spyObject5c= {
755
875
get [Symbol.toStringTag]() {
756
876
return`myValue:${this.myValue}`;
757
877
},
758
878
myValue:42
759
879
};
760
-
String(spyObject4c);
880
+
String(spyObject5c);
761
881
// "[object myValue:42]"
762
-
spyObject4c.toString();
882
+
spyObject5c.toString();
763
883
// "[object myValue:42]"
764
884
```
765
885
766
886
`Symbol.toStringTag` is intended to define a custom string value to describe the object whenever its default `toString()` operation is invoked directly, or implicitly via coercion; in its absence, the value used is `"Object"` in the common `"[object Object]"` output.
767
887
768
-
The `get ..` syntax in `spyObject4c` is defining a *getter*. That means when JS tries to access this `Symbol.toStringTag` as a property (as normal), this gettter code instead causes the function we specify to be invoked to compute the result. We can run any arbitrary logic inside this getter to dynamically determine a string *tag* for use by the default `toString()` method.
888
+
The `get ..` syntax in `spyObject5c` is defining a *getter*. That means when JS tries to access this `Symbol.toStringTag` as a property (as normal), this gettter code instead causes the function we specify to be invoked to compute the result. We can run any arbitrary logic inside this getter to dynamically determine a string *tag* for use by the default `toString()` method.
769
889
770
890
#### Overriding `ToPrimitive`
771
891
772
892
You can alternately override the whole default `ToPrimitive()` operation for any object, by setting the special symbol property `Symbol.toPrimitive` to hold a function:
773
893
774
894
```js
775
-
spyObject5= {
895
+
spyObject6= {
776
896
[Symbol.toPrimitive](hint) {
777
897
console.log(`toPrimitive(${hint}) invoked!`);
778
898
return25;
@@ -787,19 +907,19 @@ spyObject5 = {
787
907
},
788
908
};
789
909
790
-
String(spyObject5);
910
+
String(spyObject6);
791
911
// toPrimitive(string) invoked!
792
912
// "25" <--- not "10"
793
913
794
-
spyObject5+"";
914
+
spyObject6+"";
795
915
// toPrimitive(default) invoked!
796
916
// "25" <--- not "42"
797
917
798
-
Number(spyObject5);
918
+
Number(spyObject6);
799
919
// toPrimitive(number) invoked!
800
920
// 25 <--- not 42 or "25"
801
921
802
-
+spyObject5;
922
+
+spyObject6;
803
923
// toPrimitive(number) invoked!
804
924
// 25
805
925
```
@@ -808,6 +928,10 @@ As you can see, if you define this function on an object, it's used entirely in
808
928
809
929
Or you can just manually define a return value as shown above. Regardless, JS will *not* automatically invoke either `toString()` or `valueOf()` methods.
810
930
931
+
| WARNING: |
932
+
| :--- |
933
+
| As discussed prior in "No Primitive Found?", if the defined `Symbol.toPrimitive` function does not actually return a value that's a primitive, an exception will be thrown about being unable to "...convert object to primitive value". Make sure to always return an actual primitive value from such a function! |
0 commit comments