]> git.r.bdr.sh - rbdr/txt/blob - courses/lets_talk_about_javascript.md
UIpdate learning log
[rbdr/txt] / courses / lets_talk_about_javascript.md
1 # Let’s talk about Javascript.
2
3 Now, you often hear Javascript is an “Object Oriented” language. And this is true, but not in the same sense as languages like PHP or Ruby are. It’s more accurate to say that javascript uses “Prototype Inheritance”.
4
5 ## Let’s talk about prototypes
6
7 Let’s start with the humble object `{}`. An object is a collection of keys that point to a value. For example: `{isCool: true}`. This is what in other languages you might call a dictionary or a hash. Pretty much everything in the language is an object:
8
9 1. Functions? Objects
10 2. Arrays? Objects
11 3. Strings? You better believe it they’re objects.
12 4. Then there’s object which are also objects.
13
14 Objects in javascript have a very special and useful property called `__proto__`. This is known as the “prototype”, and to better understand what it does, let’s look at the following question:
15
16 *What happens when you try to retrieve a property?*
17
18 1. Check if the property exists in the object.
19 1. If it does, return that value
20 2. If it doesn’t, check if the property exists in the `__proto__`
21 1. If it does, return that value
22 2. If it doesn’t, check if the property exists in the `__proto__`
23 1. Continue this until the prototype is null
24
25 Cool. Simple enough. *Let’s build some objects then!*
26
27 ```
28 const coolPrototype = {isCool: true, expressCoolness: function () { console.log("Am I cool? You know that's " + this.isCool) }};
29 const aCoolObject = {__proto__: coolPrototype};
30 const aLonelyObject = {};
31 const anUncoolObject = {__proto__: coolPrototype, isCool: false};
32 ```
33
34 You might have noticed the use of `this` in the `expressCoolness` . Put a pin in that, we’ll come back to it later.
35
36 What’s the value of each of these statements:
37
38 ```
39 aCoolObject.isCool;
40 aLonelyObject.isCool;
41 anUncoolObject.isCool;
42 ```
43
44 What’s about these? Why?
45
46 ```
47 aCoolObject.expressCoolness();
48 aLonelyObject.expressCoolness();
49 anUncoolObject.expressCoolness();
50 ```
51
52
53 What would happen if we do the following? why?
54
55 ```
56 aLonelyObject.__proto__ = coolPrototype;
57 aLonelyObject.isCool;
58 aLonelyObject.expressCoolness();
59 ```
60
61
62 We could say that “aCoolObject” inherits from “coolPrototype”. An equivalent statement would be that “coolPrototype” is in “aCoolObject’s” prototype chain.
63
64 There’s an easy way to create objects from another objects! It’s called `Object.create`. Imagine we want to create many cool objects, we could do something like this:
65
66 ```
67 const aNewCoolObject = Object.create(coolPrototype);
68 aNewCoolObject.__proto__ === coolPrototype; // true!
69 aNewCoolObject.isCool; // true!
70 ```
71
72 Of course, when creating objects we might want to customise their behaviour, so we might end up with something like this.
73 ```
74 const createCoolObject = function (isCool) {
75 const newObject = Object.create(coolPrototype);
76 newObject.isCool = isCool;
77 return newObject;
78 };
79
80 const aCustomCoolObject = createCoolObject(false);
81 aCustomCoolObject.expressCoolness();
82 ```
83
84 You might recognise this function as a “constructor function”: It creates a new instance of an object, and lets us customise its options on creation.
85
86 In fact, you might be familiar with constructor functions in JS that use the `new` keyword. So what does the `new` keyword do?
87
88 ## Let’s talk about `new`
89
90 1. Creates a new object, and sets its `__proto__` property to be the `.prototype` property of the function (ie. `Object.create`)
91 2. Makes that available as `this` in the body of the function
92 3. Returns the new object.
93
94 So we could re-write the previous `createCoolObject` in this format as follows:
95
96 ```
97 const CoolObject = function (isCool) {
98 this.isCool = isCool;
99 };
100 CoolObject.prototype = {isCool: true, expressCoolness: function () { console.log("Am I cool? You know that's " + this.isCool) }};
101
102 const aConstructedCoolObject = new CoolObject(false);
103 aConstructedCoolObject.expressCoolness();
104 ```
105
106 So in a way `new` and constructor functions could be considered “syntax sugar” to make prototype inheritance easier and more similar to other object oriented languages. And recently you might have run into the `class` syntax, which is new syntax sugar on top of the constructor function form.
107
108 ```
109 const CoolClass = class {
110
111 constructor(isCool) {
112 this.isCool = isCool;
113 }
114
115 isCool = true
116
117 expressCoolness() {
118 console.log("Am I cool? You know that's " + this.isCool)
119 }
120 };
121
122 const aConstructedCoolObject = new CoolClass(false);
123 aConstructedCoolObject.expressCoolness();
124 ```
125
126 These are all equivalent. But there’s no magic going on in the background, just a chain of prototypes.
127
128 OK. So now we know how we can create objects and link objects to other objects by creating prototype chains.
129
130 ## Let’s talk about `this`.
131
132 This is one of the biggest sources of confusion for folks that are used to traditional object oriented program, where `this` refers to the specific instance of an object, however this is not the case for javascript, and understanding what it means is core to the language.
133
134 So, What is `this`?
135
136 Let’s look at these examples. What is the outcome of each of them?
137
138 ```
139 node
140
141 function aLonelyFunctionDeclaration () {
142 return this;
143 }
144
145 const aLonelyFunctionExpression = function () {
146 return this;
147 }
148
149 const myObject = {
150 aFunctionInAnObject: function() {
151 return this;
152 }
153 }
154
155 const anExtractedFunction = myObject.aFunctionInAnObject;
156
157 this;
158 aLonelyFunctionDeclaration();
159 aLonelyFunctionExpression();
160 myObject.aFunctionInAnObject();
161 anExtractedFunction();
162 ```
163
164 OK. So 3 of them: it was the global object. For 1 of them it was the `myObject` object.
165
166 Let’s look at this variation.
167
168 ```
169 node --use-strict // or 'use strict';
170
171 function aLonelyFunctionDeclaration () {
172 return this;
173 }
174
175 const aLonelyFunctionExpression = function () {
176 return this;
177 }
178
179 const myObject = {
180 aFunctionInAnObject: function() {
181 return this;
182 }
183 }
184
185 const anExtractedFunction = myObject.aFunctionInAnObject;
186
187 this;
188 aLonelyFunctionDeclaration();
189 aLonelyFunctionExpression();
190 myObject.aFunctionInAnObject();
191 anExtractedFunction();
192 ```
193
194
195 OK So this gives us some hints. *What can you infer about it?*
196
197 `this` depends on *how the function is called* and not how the function is defined. You could say that the function is the “caller”.
198
199 In `myObject.aFunctionInAnObject()` the caller is `myObject`
200 In `anExtractedFunction()` the caller is not defined. In strict mode this will remain as undefined. In non strict mode it will default to the global object, which might cause some undesired effects.
201
202 That’s pretty much it… well. Almost.
203
204 Consider this case:
205
206 ```
207 (function() {
208
209 "use strict";
210
211 const iRunCallbacks = function(callback) { callback(); };
212 const coolObject = {
213 isCool: true,
214 expressCoolness() {
215 console.log("Am I cool? You know that's " + this.isCool)
216 },
217 callWithCallback() {
218 iRunCallbacks(this.expressCoolness);
219 }
220 };
221
222 coolObject.callWithCallback();
223 })();
224 ```
225
226 Sometimes we want to pass functions around while still keeping the references to the object we’re using. We can control `this` in some different ways.
227
228 Option 1. Wrap the call in a function.
229
230 ```
231 (function() {
232
233 "use strict";
234
235 const iRunCallbacks = function(callback) { callback(); };
236 const coolObject = {
237 isCool: true,
238 expressCoolness() {
239 console.log("Am I cool? You know that's " + this.isCool)
240 },
241 callWithCallback() {
242
243 const myCaller = this;
244 iRunCallbacks(function () {
245 myCaller.expressCoolness()
246 });
247 }
248 };
249
250 coolObject.callWithCallback();
251 })();
252 ```
253
254 A bit verbose, but it keeps the right caller by keeping a closure to `this` inside the `myCaller` variable.
255
256 We can do better. Javascript provides the function `bind` which creates a new function with its `this` bound to whatever value you specified. `bind` can also bind arguments, it’s a very handy function. Let’s look at how it works
257
258 ```
259 (function() {
260
261 "use strict";
262
263 const iRunCallbacks = function(callback) { callback(); };
264 const coolObject = {
265 isCool: true,
266 expressCoolness() {
267 console.log("Am I cool? You know that's " + this.isCool)
268 },
269 callWithCallback() {
270
271 iRunCallbacks(this.expressCoolness.bind(this));
272 }
273 };
274
275 coolObject.callWithCallback();
276 })();
277 ```
278
279 There’s also `apply` and `call` which is pretty much the same but it also executes the function (somewhat equivalent to `myFunction.bind(…)()`).
280
281 ## Let’s talk about arrow functions
282 ES6 introduced some syntax sugar for this purpose in the form of `() =>`
283
284 ```
285 (function() {
286
287 "use strict";
288
289 const iRunCallbacks = function(callback) { callback(); };
290 const coolObject = {
291 isCool: true,
292 expressCoolness() {
293 console.log("Am I cool? You know that's " + this.isCool)
294 },
295 callWithCallback() {
296
297 iRunCallbacks(() => this.expressCoolness());
298 }
299 };
300
301 coolObject.callWithCallback();
302 })();
303 ```
304
305
306 Arrow functions are special functions that are not well suited to be methods, as they *don’t bind their own this*, don’t have arguments, can’t be used with `bind`, can’t be constructors.
307
308 Arrow functions get the `this` based on what was the `this` in the scope they were defined.
309
310 ## Let’s talk about prototypes again
311
312 The fact that it works like this, means that javascript is extremely powerful!
313
314 For example, have you ever encountered a statement like this?
315
316 `Array.prototype.slice.call(arguments)`
317
318 We’re using a function of the `Array.prototype` object in a non-array-like object. This works
319
320 There’s nothing *special* about objects in javascript other than the properties and methods they have. If your function quacks like an array, then any object in the `Array.prototype` can be used. Consider the following example:
321
322 ```
323 const notAString = {
324 called: 0,
325 toString() {
326 this.called += 1;
327 return `[${this.called}]`;
328 }
329 };
330
331 String.prototype.repeat.call(notAString, 10)
332 String.prototype.repeat.call(notAString, 10)
333 ```
334
335 Now you know everything you need to know*
336
337 There’s other interesting things you can do in objects with getters and setters (eg. read-only objects). But that is left as an exercise to the reader: [Working with objects - JavaScript | MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Working_with_Objects#defining_getters_and_setters)
338
339
340 ### Q&A I
341
342
343 ## Let’s talk about the event loop.
344
345 Another important part of javascript is knowing when things will run. While javascript is asynchronous, it’s not parallel. Tasks run one at a time in a loop known as the “Event Loop”
346
347 Every function call that is the root of the stack (ie. it’s not running from inside another function), creates a task.
348
349 Every function call from inside another function, adds to the stack.
350
351 Each message will be run *to completion*. That is, no other message will be processed until the previous one is complete.
352
353 Consider these two functions
354 ```
355 let someGlobalState = 1;
356
357 function printAndIncreaseGlobalStateByTen() {
358 console.log("Running some prints with global state", someGlobalState);
359 for (let i = 0; i < 10; ++i) {
360 printANumber(someGlobalState);
361 ++someGlobalState;
362 }
363 console.log("Prints run, new global state is", someGlobalState);
364 }
365
366 function printANumber(number) {
367
368 console.log("here's a number: ", number);
369 }
370
371 function duplicateGlobalState() {
372 someGlobalState = someGlobalState * 2;
373 console.log("Duplicated global state", someGlobalState);
374 }
375
376 printAndIncreaseGlobalStateByTen();
377 duplicateGlobalState();
378 ```
379
380 As you can see, `duplicateGlobalState` will not run until all the other calls have finished. We can remove an element from the stack and enqueue it as a message by using `setTimeout`.
381
382 ```
383 let someGlobalState = 1;
384
385 function printAndIncreaseGlobalStateByTen() {
386 console.log("Running some prints with global state", someGlobalState);
387 for (let i = 0; i < 10; ++i) {
388 setTimeout(function () {
389 printANumber(someGlobalState)
390 ++someGlobalState;
391 }, 0);
392 }
393 console.log("Prints run, new global state is", someGlobalState);
394 }
395
396 function printANumber(number) {
397
398 console.log("here's a number: ", number);
399 }
400
401 function duplicateGlobalState() {
402 someGlobalState = someGlobalState * 2;
403 console.log("Duplicated global state", someGlobalState);
404 }
405
406 printAndIncreaseGlobalStateByTen();
407 duplicateGlobalState();
408 ```
409
410 What will the outcome of this be?
411
412 You can compare the two as follows:
413
414 `[t1 [t2] [t2] [t2] [t2] [t2] [t2] [t2] [t2] [t2] [t2]] [t3]`
415 `[t1] [t2] [t2] [t2] [t2] [t2] [t2] [t2] [t2] [t2] [t2] [t3]`
416
417 Note that `setTimeout` tells you the *minimum* time before the function is executed, and not the actual time.
418
419 Why is this important? Well, the event loop has other tasks to run while your code is running:
420
421 * Calculating Styles
422 * Calculating Layout
423 * Rendering
424 * React to user interaction events
425
426 On every iteration the event loop will run all the pending tasks, and then these operations. Tasks enqueued as a result of this work will not be run until the next iteration of the loop.
427
428 If you’re blocking the event loop by having long-winded tasks, you will not be able to do any of these steps and the UI will be unresponsive :(
429
430 ## Let’s talk about `requestAnimationFrame`
431
432 For tasks that are tied to updating rendered state, you have another option that is better than `setTimeout`. `requestAnimationFrame`.
433
434 `requestAnimationFrame` tasks will only run when the browser is about to render a new frame. This can avoid unnecessary work that will not be rendered.
435
436 If non-blocking processing raw speed is important: `setTimeout`
437 If non-blocking operations should be minimised only to when we want to update the screen: `requestAnimationFrame`
438
439 ## Let’s talk about Microtasks
440
441 There’s a different queue for “Microtasks”. Microtasks are promise callbacks and callbacks from the mutation observer.
442
443 Unlike tasks, *all* microtasks are executed every time the queue is empty. This includes microtasks queued as a result of this.
444
445 This means that loops of microtasks *can block render*. Be careful when looping promise callbacks.
446
447 https://codepen.io/rbdr/pen/gOWPwzv
448
449 I really recommend watching this video for a better idea: [Before you continue to YouTube](https://www.youtube.com/watch?v=cCOL7MC4Pl0)., it explains the event loop better than I could ever.
450
451 ### Q&A II
452
453 ## Let’s talk about MDN
454 The resource I use the most when building this is MDN. You should get familiar with it because it’ll answer all your questions.
455
456 For example, learn about microtasks here: [Using microtasks in JavaScript with queueMicrotask() - Web APIs | MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API/Microtask_guide)
457 Or learn about the event loop here: [Concurrency model and the event loop - JavaScript | MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop)
458
459 Or learn about arrays here: [Array - JavaScript | MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)
460
461 One thing to note is that methods can be either `Array.<methodName>` or `Array.prototype.<methodName>`. As you might know from what we discussed, the former are methods attached directly to the Array constructor (eg. `Array.from()`) or in the prototype, which means it’s available for any instance (eg. `const array= []; array.filter() // Array.prototype.filter` )
462
463 ## Let’s talk about the Spec
464 If you want to get deeper into the behaviour, you can read the spec. This is the definition of how JS works, and can give you some specifics, and it’s surprisingly straightforward to follow. For example, how does Array.prototype.slice work? [ECMAScript® 2022 Language Specification](https://tc39.es/ecma262/multipage/indexed-collections.html#sec-array.prototype.slice)