refactor objects, add optional chaining
This commit is contained in:
parent
09a964e969
commit
b057341f6c
35 changed files with 579 additions and 435 deletions
|
@ -1,37 +0,0 @@
|
|||
**Error**!
|
||||
|
||||
Try it:
|
||||
|
||||
```js run
|
||||
let user = {
|
||||
name: "John",
|
||||
go: function() { alert(this.name) }
|
||||
}
|
||||
|
||||
(user.go)() // error!
|
||||
```
|
||||
|
||||
The error message in most browsers does not give us much of a clue about what went wrong.
|
||||
|
||||
**The error appears because a semicolon is missing after `user = {...}`.**
|
||||
|
||||
JavaScript does not auto-insert a semicolon before a bracket `(user.go)()`, so it reads the code like:
|
||||
|
||||
```js no-beautify
|
||||
let user = { go:... }(user.go)()
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
If we insert the semicolon, all is fine:
|
||||
|
||||
```js run
|
||||
let user = {
|
||||
name: "John",
|
||||
go: function() { alert(this.name) }
|
||||
}*!*;*/!*
|
||||
|
||||
(user.go)() // John
|
||||
```
|
||||
|
||||
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.
|
|
@ -1,19 +0,0 @@
|
|||
importance: 2
|
||||
|
||||
---
|
||||
|
||||
# Syntax check
|
||||
|
||||
What is the result of this code?
|
||||
|
||||
|
||||
```js no-beautify
|
||||
let user = {
|
||||
name: "John",
|
||||
go: function() { alert(this.name) }
|
||||
}
|
||||
|
||||
(user.go)()
|
||||
```
|
||||
|
||||
P.S. There's a pitfall :)
|
|
@ -1,22 +0,0 @@
|
|||
|
||||
Here's the explanations.
|
||||
|
||||
1. That's a regular object method call.
|
||||
|
||||
2. The same, parentheses do not change the order of operations here, the dot is first anyway.
|
||||
|
||||
3. Here we have a more complex call `(expression).method()`. The call works as if it were split into two lines:
|
||||
|
||||
```js no-beautify
|
||||
f = obj.go; // calculate the expression
|
||||
f(); // call what we have
|
||||
```
|
||||
|
||||
Here `f()` is executed as a function, without `this`.
|
||||
|
||||
4. The similar thing as `(3)`, to the left of the dot `.` we have an expression.
|
||||
|
||||
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.
|
||||
|
||||
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`.
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
importance: 3
|
||||
|
||||
---
|
||||
|
||||
# Explain the value of "this"
|
||||
|
||||
In the code below we intend to call `obj.go()` method 4 times in a row.
|
||||
|
||||
But calls `(1)` and `(2)` works differently from `(3)` and `(4)`. Why?
|
||||
|
||||
```js run no-beautify
|
||||
let obj, method;
|
||||
|
||||
obj = {
|
||||
go: function() { alert(this); }
|
||||
};
|
||||
|
||||
obj.go(); // (1) [object Object]
|
||||
|
||||
(obj.go)(); // (2) [object Object]
|
||||
|
||||
(method = obj.go)(); // (3) undefined
|
||||
|
||||
(obj.go || obj.stop)(); // (4) undefined
|
||||
```
|
||||
|
|
@ -233,98 +233,6 @@ The concept of run-time evaluated `this` has both pluses and minuses. On the one
|
|||
Here our position is not to judge whether this language design decision is good or bad. We'll understand how to work with it, how to get benefits and avoid problems.
|
||||
```
|
||||
|
||||
## Internals: Reference Type
|
||||
|
||||
```warn header="In-depth language feature"
|
||||
This section covers an advanced topic, to understand certain edge-cases better.
|
||||
|
||||
If you want to go on faster, it can be skipped or postponed.
|
||||
```
|
||||
|
||||
An intricate method call can lose `this`, for instance:
|
||||
|
||||
```js run
|
||||
let user = {
|
||||
name: "John",
|
||||
hi() { alert(this.name); },
|
||||
bye() { alert("Bye"); }
|
||||
};
|
||||
|
||||
user.hi(); // John (the simple call works)
|
||||
|
||||
*!*
|
||||
// now let's call user.hi or user.bye depending on the name
|
||||
(user.name == "John" ? user.hi : user.bye)(); // Error!
|
||||
*/!*
|
||||
```
|
||||
|
||||
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`.
|
||||
|
||||
Then the method is immediately called with parentheses `()`. But it doesn't work correctly!
|
||||
|
||||
As you can see, the call results in an error, because the value of `"this"` inside the call becomes `undefined`.
|
||||
|
||||
This works (object dot method):
|
||||
```js
|
||||
user.hi();
|
||||
```
|
||||
|
||||
This doesn't (evaluated method):
|
||||
```js
|
||||
(user.name == "John" ? user.hi : user.bye)(); // Error!
|
||||
```
|
||||
|
||||
Why? If we want to understand why it happens, let's get under the hood of how `obj.method()` call works.
|
||||
|
||||
Looking closely, we may notice two operations in `obj.method()` statement:
|
||||
|
||||
1. First, the dot `'.'` retrieves the property `obj.method`.
|
||||
2. Then parentheses `()` execute it.
|
||||
|
||||
So, how does the information about `this` get passed from the first part to the second one?
|
||||
|
||||
If we put these operations on separate lines, then `this` will be lost for sure:
|
||||
|
||||
```js run
|
||||
let user = {
|
||||
name: "John",
|
||||
hi() { alert(this.name); }
|
||||
}
|
||||
|
||||
*!*
|
||||
// split getting and calling the method in two lines
|
||||
let hi = user.hi;
|
||||
hi(); // Error, because this is undefined
|
||||
*/!*
|
||||
```
|
||||
|
||||
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`.
|
||||
|
||||
**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).**
|
||||
|
||||
The Reference Type is a "specification type". We can't explicitly use it, but it is used internally by the language.
|
||||
|
||||
The value of Reference Type is a three-value combination `(base, name, strict)`, where:
|
||||
|
||||
- `base` is the object.
|
||||
- `name` is the property name.
|
||||
- `strict` is true if `use strict` is in effect.
|
||||
|
||||
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:
|
||||
|
||||
```js
|
||||
// Reference Type value
|
||||
(user, "hi", true)
|
||||
```
|
||||
|
||||
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).
|
||||
|
||||
Reference type is a special "intermediary" internal type, with the purpose to pass information from dot `.` to calling parentheses `()`.
|
||||
|
||||
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`.
|
||||
|
||||
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).
|
||||
|
||||
## Arrow functions have no "this"
|
||||
|
||||
Arrow functions are special: they don't have their "own" `this`. If we reference `this` from such a function, it's taken from the outer "normal" function.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue