Skip to content

Commit 1a5852e

Browse files
authored
Merge pull request #135 from odsantos/fix-miscellaneous
Fix miscellaneous
2 parents 24dc611 + d390c2c commit 1a5852e

File tree

6 files changed

+348
-0
lines changed

6 files changed

+348
-0
lines changed
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
**Error**!
2+
3+
Try it:
4+
5+
```js run
6+
let user = {
7+
name: "John",
8+
go: function() { alert(this.name) }
9+
}
10+
11+
(user.go)() // error!
12+
```
13+
14+
The error message in most browsers does not give us much of a clue about what went wrong.
15+
16+
**The error appears because a semicolon is missing after `user = {...}`.**
17+
18+
JavaScript does not auto-insert a semicolon before a bracket `(user.go)()`, so it reads the code like:
19+
20+
```js no-beautify
21+
let user = { go:... }(user.go)()
22+
```
23+
24+
Then we can also see that such a joint expression is syntactically a call of the object `{ go: ... }` as a function with the argument `(user.go)`. And that also happens on the same line with `let user`, so the `user` object has not yet even been defined, hence the error.
25+
26+
If we insert the semicolon, all is fine:
27+
28+
```js run
29+
let user = {
30+
name: "John",
31+
go: function() { alert(this.name) }
32+
}*!*;*/!*
33+
34+
(user.go)() // John
35+
```
36+
37+
Please note that parentheses around `(user.go)` do nothing here. Usually they setup the order of operations, but here the dot `.` works first anyway, so there's no effect. Only the semicolon thing matters.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
importance: 2
2+
3+
---
4+
5+
# Syntax check
6+
7+
What is the result of this code?
8+
9+
10+
```js no-beautify
11+
let user = {
12+
name: "John",
13+
go: function() { alert(this.name) }
14+
}
15+
16+
(user.go)()
17+
```
18+
19+
P.S. There's a pitfall :)
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
2+
Here's the explanations.
3+
4+
1. That's a regular object method call.
5+
6+
2. The same, parentheses do not change the order of operations here, the dot is first anyway.
7+
8+
3. Here we have a more complex call `(expression).method()`. The call works as if it were split into two lines:
9+
10+
```js no-beautify
11+
f = obj.go; // calculate the expression
12+
f(); // call what we have
13+
```
14+
15+
Here `f()` is executed as a function, without `this`.
16+
17+
4. The similar thing as `(3)`, to the left of the dot `.` we have an expression.
18+
19+
To explain the behavior of `(3)` and `(4)` we need to recall that property accessors (dot or square brackets) return a value of the Reference Type.
20+
21+
Any operation on it except a method call (like assignment `=` or `||`) turns it into an ordinary value, which does not carry the information allowing to set `this`.
22+
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
importance: 3
2+
3+
---
4+
5+
# Explain the value of "this"
6+
7+
In the code below we intend to call `obj.go()` method 4 times in a row.
8+
9+
But calls `(1)` and `(2)` works differently from `(3)` and `(4)`. Why?
10+
11+
```js run no-beautify
12+
let obj, method;
13+
14+
obj = {
15+
go: function() { alert(this); }
16+
};
17+
18+
obj.go(); // (1) [object Object]
19+
20+
(obj.go)(); // (2) [object Object]
21+
22+
(method = obj.go)(); // (3) undefined
23+
24+
(obj.go || obj.stop)(); // (4) undefined
25+
```
26+
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
2+
# Reference Type
3+
4+
```warn header="In-depth language feature"
5+
This article covers an advanced topic, to understand certain edge-cases better.
6+
7+
It's not important. Many experienced developers live fine without knowing it. Read on if you're want to know how things work under the hood.
8+
```
9+
10+
A dynamically evaluated method call can lose `this`.
11+
12+
For instance:
13+
14+
```js run
15+
let user = {
16+
name: "John",
17+
hi() { alert(this.name); },
18+
bye() { alert("Bye"); }
19+
};
20+
21+
user.hi(); // works
22+
23+
// now let's call user.hi or user.bye depending on the name
24+
*!*
25+
(user.name == "John" ? user.hi : user.bye)(); // Error!
26+
*/!*
27+
```
28+
29+
On the last line there is a conditional operator that chooses either `user.hi` or `user.bye`. In this case the result is `user.hi`.
30+
31+
Then the method is immediately called with parentheses `()`. But it doesn't work correctly!
32+
33+
As you can see, the call results in an error, because the value of `"this"` inside the call becomes `undefined`.
34+
35+
This works (object dot method):
36+
```js
37+
user.hi();
38+
```
39+
40+
This doesn't (evaluated method):
41+
```js
42+
(user.name == "John" ? user.hi : user.bye)(); // Error!
43+
```
44+
45+
Why? If we want to understand why it happens, let's get under the hood of how `obj.method()` call works.
46+
47+
## Reference type explained
48+
49+
Looking closely, we may notice two operations in `obj.method()` statement:
50+
51+
1. First, the dot `'.'` retrieves the property `obj.method`.
52+
2. Then parentheses `()` execute it.
53+
54+
So, how does the information about `this` get passed from the first part to the second one?
55+
56+
If we put these operations on separate lines, then `this` will be lost for sure:
57+
58+
```js run
59+
let user = {
60+
name: "John",
61+
hi() { alert(this.name); }
62+
}
63+
64+
*!*
65+
// split getting and calling the method in two lines
66+
let hi = user.hi;
67+
hi(); // Error, because this is undefined
68+
*/!*
69+
```
70+
71+
Here `hi = user.hi` puts the function into the variable, and then on the last line it is completely standalone, and so there's no `this`.
72+
73+
**To make `user.hi()` calls work, JavaScript uses a trick -- the dot `'.'` returns not a function, but a value of the special [Reference Type](https://tc39.github.io/ecma262/#sec-reference-specification-type).**
74+
75+
The Reference Type is a "specification type". We can't explicitly use it, but it is used internally by the language.
76+
77+
The value of Reference Type is a three-value combination `(base, name, strict)`, where:
78+
79+
- `base` is the object.
80+
- `name` is the property name.
81+
- `strict` is true if `use strict` is in effect.
82+
83+
The result of a property access `user.hi` is not a function, but a value of Reference Type. For `user.hi` in strict mode it is:
84+
85+
```js
86+
// Reference Type value
87+
(user, "hi", true)
88+
```
89+
90+
When parentheses `()` are called on the Reference Type, they receive the full information about the object and its method, and can set the right `this` (`=user` in this case).
91+
92+
Reference type is a special "intermediary" internal type, with the purpose to pass information from dot `.` to calling parentheses `()`.
93+
94+
Any other operation like assignment `hi = user.hi` discards the reference type as a whole, takes the value of `user.hi` (a function) and passes it on. So any further operation "loses" `this`.
95+
96+
So, as the result, the value of `this` is only passed the right way if the function is called directly using a dot `obj.method()` or square brackets `obj['method']()` syntax (they do the same here). Later in this tutorial, we will learn various ways to solve this problem such as [func.bind()](/bind#solution-2-bind).
97+
98+
## Summary
99+
100+
Reference Type is an internal type of the language.
101+
102+
Reading a property, such as with dot `.` in `obj.method()` returns not exactly the property value, but a special "reference type" value that stores both the property value and the object it was taken from.
103+
104+
That's for the subsequent method call `()` to get the object and set `this` to it.
105+
106+
For all other operations, the reference type automatically becomes the property value (a function in our case).
107+
108+
The whole mechanics is hidden from our eyes. It only matters in subtle cases, such as when a method is obtained dynamically from the object, using an expression.
109+
110+
111+
112+
113+
114+
result of dot `.` isn't actually a method, but a value of `` needs a way to pass the information about `obj`
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
# BigInt
2+
3+
[recent caniuse="bigint"]
4+
5+
`BigInt` is a special numeric type that provides support for integers of arbitrary length.
6+
7+
A bigint is created by appending `n` to the end of an integer literal or by calling the function `BigInt` that creates bigints from strings, numbers etc.
8+
9+
```js
10+
const bigint = 1234567890123456789012345678901234567890n;
11+
12+
const sameBigint = BigInt("1234567890123456789012345678901234567890");
13+
14+
const bigintFromNumber = BigInt(10); // same as 10n
15+
```
16+
17+
## Math operators
18+
19+
`BigInt` can mostly be used like a regular number, for example:
20+
21+
```js run
22+
alert(1n + 2n); // 3
23+
24+
alert(5n / 2n); // 2
25+
```
26+
27+
Please note: the division `5/2` returns the result rounded towards zero, without the decimal part. All operations on bigints return bigints.
28+
29+
We can't mix bigints and regular numbers:
30+
31+
```js run
32+
alert(1n + 2); // Error: Cannot mix BigInt and other types
33+
```
34+
35+
We should explicitly convert them if needed: using either `BigInt()` or `Number()`, like this:
36+
37+
```js run
38+
let bigint = 1n;
39+
let number = 2;
40+
41+
// number to bigint
42+
alert(bigint + BigInt(number)); // 3
43+
44+
// bigint to number
45+
alert(Number(bigint) + number); // 3
46+
```
47+
48+
The conversion operations are always silent, never give errors, but if the bigint is too huge and won't fit the number type, then extra bits will be cut off, so we should be careful doing such conversion.
49+
50+
````smart header="The unary plus is not supported on bigints"
51+
The unary plus operator `+value` is a well-known way to convert `value` to a number.
52+
53+
On bigints it's not supported, to avoid confusion:
54+
```js run
55+
let bigint = 1n;
56+
57+
alert( +bigint ); // error
58+
```
59+
So we should use `Number()` to convert a bigint to a number.
60+
````
61+
62+
## Comparisons
63+
64+
Comparisons, such as `<`, `>` work with bigints and numbers just fine:
65+
66+
```js run
67+
alert( 2n > 1n ); // true
68+
69+
alert( 2n > 1 ); // true
70+
```
71+
72+
Please note though, as numbers and bigints belong to different types, they can be equal `==`, but not strictly equal `===`:
73+
74+
```js run
75+
alert( 1 == 1n ); // true
76+
77+
alert( 1 === 1n ); // false
78+
```
79+
80+
## Boolean operations
81+
82+
When inside `if` or other boolean operations, bigints behave like numbers.
83+
84+
For instance, in `if`, bigint `0n` is falsy, other values are truthy:
85+
86+
```js run
87+
if (0n) {
88+
// never executes
89+
}
90+
```
91+
92+
Boolean operators, such as `||`, `&&` and others also work with bigints similar to numbers:
93+
94+
```js run
95+
alert( 1n || 2 ); // 1 (1n is considered truthy)
96+
97+
alert( 0n || 2 ); // 2 (0n is considered falsy)
98+
```
99+
100+
## Polyfills
101+
102+
Polyfilling bigints is tricky. The reason is that many JavaScript operators, such as `+`, `-` and so on behave differently with bigints compared to regular numbers.
103+
104+
For example, division of bigints always returns a bigint (rounded if necessary).
105+
106+
To emulate such behavior, a polyfill would need to analyze the code and replace all such operators with its functions. But doing so is cumbersome and would cost a lot of performance.
107+
108+
So, there's no well-known good polyfill.
109+
110+
Although, the other way around is proposed by the developers of [JSBI](https://github.com/GoogleChromeLabs/jsbi) library.
111+
112+
This library implements big numbers using its own methods. We can use them instead of native bigints:
113+
114+
| Operation | native `BigInt` | JSBI |
115+
|-----------|-----------------|------|
116+
| Creation from Number | `a = BigInt(789)` | `a = JSBI.BigInt(789)` |
117+
| Addition | `c = a + b` | `c = JSBI.add(a, b)` |
118+
| Subtraction | `c = a - b` | `c = JSBI.subtract(a, b)` |
119+
| ... | ... | ... |
120+
121+
...And then use the polyfill (Babel plugin) to convert JSBI calls to native bigints for those browsers that support them.
122+
123+
In other words, this approach suggests that we write code in JSBI instead of native bigints. But JSBI works with numbers as with bigints internally, emulates them closely following the specification, so the code will be "bigint-ready".
124+
125+
We can use such JSBI code "as is" for engines that don't support bigints and for those that do support - the polyfill will convert the calls to native bigints.
126+
127+
## References
128+
129+
- [MDN docs on BigInt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt).
130+
- [Specification](https://tc39.es/ecma262/#sec-bigint-objects).

0 commit comments

Comments
 (0)