|
| 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` |
0 commit comments