|
4 | 4 | | NOTE: |
|
5 | 5 | | :--- |
|
6 | 6 | | Work in progress |
|
7 |
| - |
8 |
| -. |
9 |
| - |
10 |
| -. |
11 |
| - |
12 |
| -. |
13 |
| - |
14 |
| -. |
15 |
| - |
16 |
| -. |
17 |
| - |
18 |
| -. |
19 |
| - |
20 |
| -. |
21 |
| - |
22 |
| ----- |
23 |
| - |
24 |
| -| NOTE: | |
25 |
| -| :--- | |
26 |
| -| Everything below here is previous text from 1st edition, and is only here for reference while 2nd edition work is underway. **Please ignore this stuff.** | |
27 |
| - |
28 |
| -One of the most confused mechanisms in JavaScript is the `this` keyword. It's a special identifier keyword that's automatically defined in the scope of every function, but what exactly it refers to bedevils even seasoned JavaScript developers. |
29 |
| - |
30 |
| -> Any sufficiently *advanced* technology is indistinguishable from magic. -- Arthur C. Clarke |
31 |
| -
|
32 |
| -JavaScript's `this` mechanism isn't actually *that* advanced, but developers often paraphrase that quote in their own mind by inserting "complex" or "confusing", and there's no question that without lack of clear understanding, `this` can seem downright magical in *your* confusion. |
33 |
| - |
34 |
| -**Note:** The word "this" is a terribly common pronoun in general discourse. So, it can be very difficult, especially verbally, to determine whether we are using "this" as a pronoun or using it to refer to the actual keyword identifier. For clarity, I will always use `this` to refer to the special keyword, and "this" or *this* or this otherwise. |
35 |
| - |
36 |
| -## Why `this`? |
37 |
| - |
38 |
| -If the `this` mechanism is so confusing, even to seasoned JavaScript developers, one may wonder why it's even useful? Is it more trouble than it's worth? Before we jump into the *how*, we should examine the *why*. |
39 |
| - |
40 |
| -Let's try to illustrate the motivation and utility of `this`: |
41 |
| - |
42 |
| -```js |
43 |
| -function identify() { |
44 |
| - return this.name.toUpperCase(); |
45 |
| -} |
46 |
| - |
47 |
| -function speak() { |
48 |
| - var greeting = "Hello, I'm " + identify.call( this ); |
49 |
| - console.log( greeting ); |
50 |
| -} |
51 |
| - |
52 |
| -var me = { |
53 |
| - name: "Kyle" |
54 |
| -}; |
55 |
| - |
56 |
| -var you = { |
57 |
| - name: "Reader" |
58 |
| -}; |
59 |
| - |
60 |
| -identify.call( me ); // KYLE |
61 |
| -identify.call( you ); // READER |
62 |
| - |
63 |
| -speak.call( me ); // Hello, I'm KYLE |
64 |
| -speak.call( you ); // Hello, I'm READER |
65 |
| -``` |
66 |
| - |
67 |
| -If the *how* of this snippet confuses you, don't worry! We'll get to that shortly. Just set those questions aside briefly so we can look into the *why* more clearly. |
68 |
| - |
69 |
| -This code snippet allows the `identify()` and `speak()` functions to be re-used against multiple *context* (`me` and `you`) objects, rather than needing a separate version of the function for each object. |
70 |
| - |
71 |
| -Instead of relying on `this`, you could have explicitly passed in a context object to both `identify()` and `speak()`. |
72 |
| - |
73 |
| -```js |
74 |
| -function identify(context) { |
75 |
| - return context.name.toUpperCase(); |
76 |
| -} |
77 |
| - |
78 |
| -function speak(context) { |
79 |
| - var greeting = "Hello, I'm " + identify( context ); |
80 |
| - console.log( greeting ); |
81 |
| -} |
82 |
| - |
83 |
| -identify( you ); // READER |
84 |
| -speak( me ); // Hello, I'm KYLE |
85 |
| -``` |
86 |
| - |
87 |
| -However, the `this` mechanism provides a more elegant way of implicitly "passing along" an object reference, leading to cleaner API design and easier re-use. |
88 |
| - |
89 |
| -The more complex your usage pattern is, the more clearly you'll see that passing context around as an explicit parameter is often messier than passing around a `this` context. When we explore objects and prototypes, you will see the helpfulness of a collection of functions being able to automatically reference the proper context object. |
90 |
| - |
91 |
| -## Confusions |
92 |
| - |
93 |
| -We'll soon begin to explain how `this` *actually* works, but first we must dispel some misconceptions about how it *doesn't* actually work. |
94 |
| - |
95 |
| -The name "this" creates confusion when developers try to think about it too literally. There are two meanings often assumed, but both are incorrect. |
96 |
| - |
97 |
| -### Itself |
98 |
| - |
99 |
| -The first common temptation is to assume `this` refers to the function itself. That's a reasonable grammatical inference, at least. |
100 |
| - |
101 |
| -Why would you want to refer to a function from inside itself? The most common reasons would be things like recursion (calling a function from inside itself) or having an event handler that can unbind itself when it's first called. |
102 |
| - |
103 |
| -Developers new to JS's mechanisms often think that referencing the function as an object (all functions in JavaScript are objects!) lets you store *state* (values in properties) between function calls. While this is certainly possible and has some limited uses, the rest of the book will expound on many other patterns for *better* places to store state besides the function object. |
104 |
| - |
105 |
| -But for just a moment, we'll explore that pattern, to illustrate how `this` doesn't let a function get a reference to itself like we might have assumed. |
106 |
| - |
107 |
| -Consider the following code, where we attempt to track how many times a function (`foo`) was called: |
108 |
| - |
109 |
| -```js |
110 |
| -function foo(num) { |
111 |
| - console.log( "foo: " + num ); |
112 |
| - |
113 |
| - // keep track of how many times `foo` is called |
114 |
| - this.count++; |
115 |
| -} |
116 |
| - |
117 |
| -foo.count = 0; |
118 |
| - |
119 |
| -var i; |
120 |
| - |
121 |
| -for (i=0; i<10; i++) { |
122 |
| - if (i > 5) { |
123 |
| - foo( i ); |
124 |
| - } |
125 |
| -} |
126 |
| -// foo: 6 |
127 |
| -// foo: 7 |
128 |
| -// foo: 8 |
129 |
| -// foo: 9 |
130 |
| - |
131 |
| -// how many times was `foo` called? |
132 |
| -console.log( foo.count ); // 0 -- WTF? |
133 |
| -``` |
134 |
| - |
135 |
| -`foo.count` is *still* `0`, even though the four `console.log` statements clearly indicate `foo(..)` was in fact called four times. The frustration stems from a *too literal* interpretation of what `this` (in `this.count++`) means. |
136 |
| - |
137 |
| -When the code executes `foo.count = 0`, indeed it's adding a property `count` to the function object `foo`. But for the `this.count` reference inside of the function, `this` is not in fact pointing *at all* to that function object, and so even though the property names are the same, the root objects are different, and confusion ensues. |
138 |
| - |
139 |
| -**Note:** A responsible developer *should* ask at this point, "If I was incrementing a `count` property but it wasn't the one I expected, which `count` *was* I incrementing?" In fact, were she to dig deeper, she would find that she had accidentally created a global variable `count` (see Chapter 2 for *how* that happened!), and it currently has the value `NaN`. Of course, once she identifies this peculiar outcome, she then has a whole other set of questions: "How was it global, and why did it end up `NaN` instead of some proper count value?" (see Chapter 2). |
140 |
| - |
141 |
| -Instead of stopping at this point and digging into why the `this` reference doesn't seem to be behaving as *expected*, and answering those tough but important questions, many developers simply avoid the issue altogether, and hack toward some other solution, such as creating another object to hold the `count` property: |
142 |
| - |
143 |
| -```js |
144 |
| -function foo(num) { |
145 |
| - console.log( "foo: " + num ); |
146 |
| - |
147 |
| - // keep track of how many times `foo` is called |
148 |
| - data.count++; |
149 |
| -} |
150 |
| - |
151 |
| -var data = { |
152 |
| - count: 0 |
153 |
| -}; |
154 |
| - |
155 |
| -var i; |
156 |
| - |
157 |
| -for (i=0; i<10; i++) { |
158 |
| - if (i > 5) { |
159 |
| - foo( i ); |
160 |
| - } |
161 |
| -} |
162 |
| -// foo: 6 |
163 |
| -// foo: 7 |
164 |
| -// foo: 8 |
165 |
| -// foo: 9 |
166 |
| - |
167 |
| -// how many times was `foo` called? |
168 |
| -console.log( data.count ); // 4 |
169 |
| -``` |
170 |
| - |
171 |
| -While it is true that this approach "solves" the problem, unfortunately it simply ignores the real problem -- lack of understanding what `this` means and how it works -- and instead falls back to the comfort zone of a more familiar mechanism: lexical scope. |
172 |
| - |
173 |
| -**Note:** Lexical scope is a perfectly fine and useful mechanism; I am not belittling the use of it, by any means (see *"Scope & Closures"* title of this book series). But constantly *guessing* at how to use `this`, and usually being *wrong*, is not a good reason to retreat back to lexical scope and never learn *why* `this` eludes you. |
174 |
| - |
175 |
| -To reference a function object from inside itself, `this` by itself will typically be insufficient. You generally need a reference to the function object via a lexical identifier (variable) that points at it. |
176 |
| - |
177 |
| -Consider these two functions: |
178 |
| - |
179 |
| -```js |
180 |
| -function foo() { |
181 |
| - foo.count = 4; // `foo` refers to itself |
182 |
| -} |
183 |
| - |
184 |
| -setTimeout( function(){ |
185 |
| - // anonymous function (no name), cannot |
186 |
| - // refer to itself |
187 |
| -}, 10 ); |
188 |
| -``` |
189 |
| - |
190 |
| -In the first function, called a "named function", `foo` is a reference that can be used to refer to the function from inside itself. |
191 |
| - |
192 |
| -But in the second example, the function callback passed to `setTimeout(..)` has no name identifier (so called an "anonymous function"), so there's no proper way to refer to the function object itself. |
193 |
| - |
194 |
| -**Note:** The old-school but now deprecated and frowned-upon `arguments.callee` reference inside a function *also* points to the function object of the currently executing function. This reference is typically the only way to access an anonymous function's object from inside itself. The best approach, however, is to avoid the use of anonymous functions altogether, at least for those which require a self-reference, and instead use a named function (expression). `arguments.callee` is deprecated and should not be used. |
195 |
| - |
196 |
| -So another solution to our running example would have been to use the `foo` identifier as a function object reference in each place, and not use `this` at all, which *works*: |
197 |
| - |
198 |
| -```js |
199 |
| -function foo(num) { |
200 |
| - console.log( "foo: " + num ); |
201 |
| - |
202 |
| - // keep track of how many times `foo` is called |
203 |
| - foo.count++; |
204 |
| -} |
205 |
| - |
206 |
| -foo.count = 0; |
207 |
| - |
208 |
| -var i; |
209 |
| - |
210 |
| -for (i=0; i<10; i++) { |
211 |
| - if (i > 5) { |
212 |
| - foo( i ); |
213 |
| - } |
214 |
| -} |
215 |
| -// foo: 6 |
216 |
| -// foo: 7 |
217 |
| -// foo: 8 |
218 |
| -// foo: 9 |
219 |
| - |
220 |
| -// how many times was `foo` called? |
221 |
| -console.log( foo.count ); // 4 |
222 |
| -``` |
223 |
| - |
224 |
| -However, that approach similarly side-steps *actual* understanding of `this` and relies entirely on the lexical scoping of variable `foo`. |
225 |
| - |
226 |
| -Yet another way of approaching the issue is to force `this` to actually point at the `foo` function object: |
227 |
| - |
228 |
| -```js |
229 |
| -function foo(num) { |
230 |
| - console.log( "foo: " + num ); |
231 |
| - |
232 |
| - // keep track of how many times `foo` is called |
233 |
| - // Note: `this` IS actually `foo` now, based on |
234 |
| - // how `foo` is called (see below) |
235 |
| - this.count++; |
236 |
| -} |
237 |
| - |
238 |
| -foo.count = 0; |
239 |
| - |
240 |
| -var i; |
241 |
| - |
242 |
| -for (i=0; i<10; i++) { |
243 |
| - if (i > 5) { |
244 |
| - // using `call(..)`, we ensure the `this` |
245 |
| - // points at the function object (`foo`) itself |
246 |
| - foo.call( foo, i ); |
247 |
| - } |
248 |
| -} |
249 |
| -// foo: 6 |
250 |
| -// foo: 7 |
251 |
| -// foo: 8 |
252 |
| -// foo: 9 |
253 |
| - |
254 |
| -// how many times was `foo` called? |
255 |
| -console.log( foo.count ); // 4 |
256 |
| -``` |
257 |
| - |
258 |
| -**Instead of avoiding `this`, we embrace it.** We'll explain in a little bit *how* such techniques work much more completely, so don't worry if you're still a bit confused! |
259 |
| - |
260 |
| -### Its Scope |
261 |
| - |
262 |
| -The next most common misconception about the meaning of `this` is that it somehow refers to the function's scope. It's a tricky question, because in one sense there is some truth, but in the other sense, it's quite misguided. |
263 |
| - |
264 |
| -To be clear, `this` does not, in any way, refer to a function's **lexical scope**. It is true that internally, scope is kind of like an object with properties for each of the available identifiers. But the scope "object" is not accessible to JavaScript code. It's an inner part of the *Engine*'s implementation. |
265 |
| - |
266 |
| -Consider code which attempts (and fails!) to cross over the boundary and use `this` to implicitly refer to a function's lexical scope: |
267 |
| - |
268 |
| -```js |
269 |
| -function foo() { |
270 |
| - var a = 2; |
271 |
| - this.bar(); |
272 |
| -} |
273 |
| - |
274 |
| -function bar() { |
275 |
| - console.log( this.a ); |
276 |
| -} |
277 |
| - |
278 |
| -foo(); //undefined |
279 |
| -``` |
280 |
| - |
281 |
| -There's more than one mistake in this snippet. While it may seem contrived, the code you see is a distillation of actual real-world code that has been exchanged in public community help forums. It's a wonderful (if not sad) illustration of just how misguided `this` assumptions can be. |
282 |
| - |
283 |
| -Firstly, an attempt is made to reference the `bar()` function via `this.bar()`. It is almost certainly an *accident* that it works, but we'll explain the *how* of that shortly. The most natural way to have invoked `bar()` would have been to omit the leading `this.` and just make a lexical reference to the identifier. |
284 |
| - |
285 |
| -However, the developer who writes such code is attempting to use `this` to create a bridge between the lexical scopes of `foo()` and `bar()`, so that `bar()` has access to the variable `a` in the inner scope of `foo()`. **No such bridge is possible.** You cannot use a `this` reference to look something up in a lexical scope. It is not possible. |
286 |
| - |
287 |
| -Every time you feel yourself trying to mix lexical scope look-ups with `this`, remind yourself: *there is no bridge*. |
288 |
| - |
289 |
| -## What's `this`? |
290 |
| - |
291 |
| -Having set aside various incorrect assumptions, let us now turn our attention to how the `this` mechanism really works. |
292 |
| - |
293 |
| -We said earlier that `this` is not an author-time binding but a runtime binding. It is contextual based on the conditions of the function's invocation. `this` binding has nothing to do with where a function is declared, but has instead everything to do with the manner in which the function is called. |
294 |
| - |
295 |
| -When a function is invoked, an activation record, otherwise known as an execution context, is created. This record contains information about where the function was called from (the call-stack), *how* the function was invoked, what parameters were passed, etc. One of the properties of this record is the `this` reference which will be used for the duration of that function's execution. |
296 |
| - |
297 |
| -In the next chapter, we will learn to find a function's **call-site** to determine how its execution will bind `this`. |
298 |
| - |
299 |
| -## Review (TL;DR) |
300 |
| - |
301 |
| -`this` binding is a constant source of confusion for the JavaScript developer who does not take the time to learn how the mechanism actually works. Guesses, trial-and-error, and blind copy-n-paste from Stack Overflow answers is not an effective or proper way to leverage *this* important `this` mechanism. |
302 |
| - |
303 |
| -To learn `this`, you first have to learn what `this` is *not*, despite any assumptions or misconceptions that may lead you down those paths. `this` is neither a reference to the function itself, nor is it a reference to the function's *lexical* scope. |
304 |
| - |
305 |
| -`this` is actually a binding that is made when a function is invoked, and *what* it references is determined entirely by the call-site where the function is called. |
0 commit comments