minor fixes
This commit is contained in:
parent
75f5b8af14
commit
c714756f8f
1 changed files with 30 additions and 27 deletions
|
@ -7,11 +7,11 @@ JavaScript doesn't exactly allow to customize how operators work on objects. Unl
|
||||||
|
|
||||||
In case of such operations, objects are auto-converted to primitives, and then the operation is carried out over these primitives and results in a primitive value.
|
In case of such operations, objects are auto-converted to primitives, and then the operation is carried out over these primitives and results in a primitive value.
|
||||||
|
|
||||||
That's an important limitation, as the result of `obj1 + obj2` can't be another object!
|
That's an important limitation: the result of `obj1 + obj2` (or another math operation) can't be another object!
|
||||||
|
|
||||||
E.g. we can't make objects representing vectors or matrices (or achievements or whatever), add them and expect a "summed" object as the result. Such architectural feats are automatically "off the board".
|
E.g. we can't make objects representing vectors or matrices (or achievements or whatever), add them and expect a "summed" object as the result. Such architectural feats are automatically "off the board".
|
||||||
|
|
||||||
So, because we can't do much here, there's no maths with objects in real projects. When it happens, it's usually because of a coding mistake.
|
So, because we can't technically do much here, there's no maths with objects in real projects. When it happens, with rare exceptions, it's because of a coding mistake.
|
||||||
|
|
||||||
In this chapter we'll cover how an object converts to primitive and how to customize it.
|
In this chapter we'll cover how an object converts to primitive and how to customize it.
|
||||||
|
|
||||||
|
@ -24,15 +24,19 @@ We have two purposes:
|
||||||
|
|
||||||
In the chapter <info:type-conversions> we've seen the rules for numeric, string and boolean conversions of primitives. But we left a gap for objects. Now, as we know about methods and symbols it becomes possible to fill it.
|
In the chapter <info:type-conversions> we've seen the rules for numeric, string and boolean conversions of primitives. But we left a gap for objects. Now, as we know about methods and symbols it becomes possible to fill it.
|
||||||
|
|
||||||
1. All objects are `true` in a boolean context. There are only numeric and string conversions.
|
1. There's no conversion to boolean. All objects are `true` in a boolean context, as simple as that. There exist only numeric and string conversions.
|
||||||
2. The numeric conversion happens when we subtract objects or apply mathematical functions. For instance, `Date` objects (to be covered in the chapter <info:date>) can be subtracted, and the result of `date1 - date2` is the time difference between two dates.
|
2. The numeric conversion happens when we subtract objects or apply mathematical functions. For instance, `Date` objects (to be covered in the chapter <info:date>) can be subtracted, and the result of `date1 - date2` is the time difference between two dates.
|
||||||
3. As for the string conversion -- it usually happens when we output an object like `alert(obj)` and in similar contexts.
|
3. As for the string conversion -- it usually happens when we output an object with `alert(obj)` and in similar contexts.
|
||||||
|
|
||||||
We can fine-tune string and numeric conversion, using special object methods.
|
We can implement string and numeric conversion by ourselves, using special object methods.
|
||||||
|
|
||||||
There are three variants of type conversion, that happen in various situations.
|
Now let's get into technical details, because it's the only way to cover the topic in-depth.
|
||||||
|
|
||||||
They're called "hints", as described in the [specification](https://tc39.github.io/ecma262/#sec-toprimitive):
|
## Hints
|
||||||
|
|
||||||
|
How does JavaScript decide which conversion to apply?
|
||||||
|
|
||||||
|
There are three variants of type conversion, that happen in various situations. They're called "hints", as described in the [specification](https://tc39.github.io/ecma262/#sec-toprimitive):
|
||||||
|
|
||||||
`"string"`
|
`"string"`
|
||||||
: For an object-to-string conversion, when we're doing an operation on an object that expects a string, like `alert`:
|
: For an object-to-string conversion, when we're doing an operation on an object that expects a string, like `alert`:
|
||||||
|
@ -60,10 +64,12 @@ They're called "hints", as described in the [specification](https://tc39.github.
|
||||||
let greater = user1 > user2;
|
let greater = user1 > user2;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Most built-in mathematical functions also include such conversion.
|
||||||
|
|
||||||
`"default"`
|
`"default"`
|
||||||
: Occurs in rare cases when the operator is "not sure" what type to expect.
|
: Occurs in rare cases when the operator is "not sure" what type to expect.
|
||||||
|
|
||||||
For instance, binary plus `+` can work both with strings (concatenates them) and numbers (adds them), so both strings and numbers would do. So if a binary plus gets an object as an argument, it uses the `"default"` hint to convert it.
|
For instance, binary plus `+` can work both with strings (concatenates them) and numbers (adds them). So if a binary plus gets an object as an argument, it uses the `"default"` hint to convert it.
|
||||||
|
|
||||||
Also, if an object is compared using `==` with a string, number or a symbol, it's also unclear which conversion should be done, so the `"default"` hint is used.
|
Also, if an object is compared using `==` with a string, number or a symbol, it's also unclear which conversion should be done, so the `"default"` hint is used.
|
||||||
|
|
||||||
|
@ -77,21 +83,19 @@ They're called "hints", as described in the [specification](https://tc39.github.
|
||||||
|
|
||||||
The greater and less comparison operators, such as `<` `>`, can work with both strings and numbers too. Still, they use the `"number"` hint, not `"default"`. That's for historical reasons.
|
The greater and less comparison operators, such as `<` `>`, can work with both strings and numbers too. Still, they use the `"number"` hint, not `"default"`. That's for historical reasons.
|
||||||
|
|
||||||
In practice though, we don't need to remember these peculiar details, because all built-in objects except for one case (`Date` object, we'll learn it later) implement `"default"` conversion the same way as `"number"`. And we can do the same.
|
In practice though, things are a bit simpler.
|
||||||
|
|
||||||
```smart header="No `\"boolean\"` hint"
|
All built-in objects except for one case (`Date` object, we'll learn it later) implement `"default"` conversion the same way as `"number"`. And we probably should do the same.
|
||||||
Please note -- there are only three hints. It's that simple.
|
|
||||||
|
|
||||||
There is no "boolean" hint (all objects are `true` in boolean context) or anything else. And if we treat `"default"` and `"number"` the same, like most built-ins do, then there are only two conversions.
|
Still, it's important to know about all 3 hints, soon we'll see why.
|
||||||
```
|
|
||||||
|
|
||||||
**To do the conversion, JavaScript tries to find and call three object methods:**
|
**To do the conversion, JavaScript tries to find and call three object methods:**
|
||||||
|
|
||||||
1. Call `obj[Symbol.toPrimitive](hint)` - the method with the symbolic key `Symbol.toPrimitive` (system symbol), if such method exists,
|
1. Call `obj[Symbol.toPrimitive](hint)` - the method with the symbolic key `Symbol.toPrimitive` (system symbol), if such method exists,
|
||||||
2. Otherwise if hint is `"string"`
|
2. Otherwise if hint is `"string"`
|
||||||
- try `obj.toString()` and `obj.valueOf()`, whatever exists.
|
- try calling `obj.toString()` or `obj.valueOf()`, whatever exists.
|
||||||
3. Otherwise if hint is `"number"` or `"default"`
|
3. Otherwise if hint is `"number"` or `"default"`
|
||||||
- try `obj.valueOf()` and `obj.toString()`, whatever exists.
|
- try calling `obj.valueOf()` or `obj.toString()`, whatever exists.
|
||||||
|
|
||||||
## Symbol.toPrimitive
|
## Symbol.toPrimitive
|
||||||
|
|
||||||
|
@ -128,12 +132,11 @@ alert(user + 500); // hint: default -> 1500
|
||||||
|
|
||||||
As we can see from the code, `user` becomes a self-descriptive string or a money amount, depending on the conversion. The single method `user[Symbol.toPrimitive]` handles all conversion cases.
|
As we can see from the code, `user` becomes a self-descriptive string or a money amount, depending on the conversion. The single method `user[Symbol.toPrimitive]` handles all conversion cases.
|
||||||
|
|
||||||
|
|
||||||
## toString/valueOf
|
## toString/valueOf
|
||||||
|
|
||||||
If there's no `Symbol.toPrimitive` then JavaScript tries to find methods `toString` and `valueOf`:
|
If there's no `Symbol.toPrimitive` then JavaScript tries to find methods `toString` and `valueOf`:
|
||||||
|
|
||||||
- For the "string" hint: `toString`, and if it doesn't exist, then `valueOf` (so `toString` has the priority for string conversions).
|
- For the `"string"` hint: call `toString` method, and if it doesn't exist, then `valueOf` (so `toString` has the priority for string conversions).
|
||||||
- For other hints: `valueOf`, and if it doesn't exist, then `toString` (so `valueOf` has the priority for maths).
|
- For other hints: `valueOf`, and if it doesn't exist, then `toString` (so `valueOf` has the priority for maths).
|
||||||
|
|
||||||
Methods `toString` and `valueOf` come from ancient times. They are not symbols (symbols did not exist that long ago), but rather "regular" string-named methods. They provide an alternative "old-style" way to implement the conversion.
|
Methods `toString` and `valueOf` come from ancient times. They are not symbols (symbols did not exist that long ago), but rather "regular" string-named methods. They provide an alternative "old-style" way to implement the conversion.
|
||||||
|
@ -207,23 +210,23 @@ In the absence of `Symbol.toPrimitive` and `valueOf`, `toString` will handle all
|
||||||
|
|
||||||
The important thing to know about all primitive-conversion methods is that they do not necessarily return the "hinted" primitive.
|
The important thing to know about all primitive-conversion methods is that they do not necessarily return the "hinted" primitive.
|
||||||
|
|
||||||
There is no control whether `toString` returns exactly a string, or whether `Symbol.toPrimitive` method returns a number for a hint `"number"`.
|
There is no control whether `toString` returns exactly a string, or whether `Symbol.toPrimitive` method returns a number for the hint `"number"`.
|
||||||
|
|
||||||
The only mandatory thing: these methods must return a primitive, not an object.
|
The only mandatory thing: these methods must return a primitive, not an object.
|
||||||
|
|
||||||
```smart header="Historical notes"
|
```smart header="Historical notes"
|
||||||
For historical reasons, if `toString` or `valueOf` returns an object, there's no error, but such value is ignored (like if the method didn't exist). That's because in ancient times there was no good "error" concept in JavaScript.
|
For historical reasons, if `toString` or `valueOf` returns an object, there's no error, but such value is ignored (like if the method didn't exist). That's because in ancient times there was no good "error" concept in JavaScript.
|
||||||
|
|
||||||
In contrast, `Symbol.toPrimitive` *must* return a primitive, otherwise there will be an error.
|
In contrast, `Symbol.toPrimitive` is stricter, it *must* return a primitive, otherwise there will be an error.
|
||||||
```
|
```
|
||||||
|
|
||||||
## Further conversions
|
## Further conversions
|
||||||
|
|
||||||
As we know already, many operators and functions perform type conversions, e.g. multiplication `*` converts operands to numbers.
|
As we know already, many operators and functions perform type conversions, e.g. multiplication `*` converts operands to numbers.
|
||||||
|
|
||||||
If we pass an object as an argument, then there are two stages:
|
If we pass an object as an argument, then there are two stages of calculations:
|
||||||
1. The object is converted to a primitive (using the rules described above).
|
1. The object is converted to a primitive (using the rules described above).
|
||||||
2. If the resulting primitive isn't of the right type, it's converted.
|
2. If the necessary for further calculations, the resulting primitive is also converted.
|
||||||
|
|
||||||
For instance:
|
For instance:
|
||||||
|
|
||||||
|
@ -260,18 +263,18 @@ The object-to-primitive conversion is called automatically by many built-in func
|
||||||
There are 3 types (hints) of it:
|
There are 3 types (hints) of it:
|
||||||
- `"string"` (for `alert` and other operations that need a string)
|
- `"string"` (for `alert` and other operations that need a string)
|
||||||
- `"number"` (for maths)
|
- `"number"` (for maths)
|
||||||
- `"default"` (few operators)
|
- `"default"` (few operators, usually objects implement it the same way as `"number"`)
|
||||||
|
|
||||||
The specification describes explicitly which operator uses which hint. There are very few operators that "don't know what to expect" and use the `"default"` hint. Usually for built-in objects `"default"` hint is handled the same way as `"number"`, so in practice the last two are often merged together.
|
The specification describes explicitly which operator uses which hint.
|
||||||
|
|
||||||
The conversion algorithm is:
|
The conversion algorithm is:
|
||||||
|
|
||||||
1. Call `obj[Symbol.toPrimitive](hint)` if the method exists,
|
1. Call `obj[Symbol.toPrimitive](hint)` if the method exists,
|
||||||
2. Otherwise if hint is `"string"`
|
2. Otherwise if hint is `"string"`
|
||||||
- try `obj.toString()` and `obj.valueOf()`, whatever exists.
|
- try calling `obj.toString()` or `obj.valueOf()`, whatever exists.
|
||||||
3. Otherwise if hint is `"number"` or `"default"`
|
3. Otherwise if hint is `"number"` or `"default"`
|
||||||
- try `obj.valueOf()` and `obj.toString()`, whatever exists.
|
- try calling `obj.valueOf()` or `obj.toString()`, whatever exists.
|
||||||
|
|
||||||
|
All these methods must return a primitive to work (if defined).
|
||||||
|
|
||||||
In practice, it's often enough to implement only `obj.toString()` as a "catch-all" method for string conversions that should return a "human-readable" representation of an object, for logging or debugging purposes.
|
In practice, it's often enough to implement only `obj.toString()` as a "catch-all" method for string conversions that should return a "human-readable" representation of an object, for logging or debugging purposes.
|
||||||
|
|
||||||
As for math operations, JavaScript doesn't provide a way to "override" them using methods, so real life projects rarely use them on objects.
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue