1 # Let’s talk about Javascript.
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”.
5 ## Let’s talk about prototypes
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:
11 3. Strings? You better believe it they’re objects.
12 4. Then there’s object which are also objects.
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:
16 *What happens when you try to retrieve a property?*
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
25 Cool. Simple enough. *Let’s build some objects then!*
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};
34 You might have noticed the use of `this` in the `expressCoolness` . Put a pin in that, we’ll come back to it later.
36 What’s the value of each of these statements:
41 anUncoolObject.isCool;
44 What’s about these? Why?
47 aCoolObject.expressCoolness();
48 aLonelyObject.expressCoolness();
49 anUncoolObject.expressCoolness();
53 What would happen if we do the following? why?
56 aLonelyObject.__proto__ = coolPrototype;
58 aLonelyObject.expressCoolness();
62 We could say that “aCoolObject” inherits from “coolPrototype”. An equivalent statement would be that “coolPrototype” is in “aCoolObject’s” prototype chain.
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:
67 const aNewCoolObject = Object.create(coolPrototype);
68 aNewCoolObject.__proto__ === coolPrototype; // true!
69 aNewCoolObject.isCool; // true!
72 Of course, when creating objects we might want to customise their behaviour, so we might end up with something like this.
74 const createCoolObject = function (isCool) {
75 const newObject = Object.create(coolPrototype);
76 newObject.isCool = isCool;
80 const aCustomCoolObject = createCoolObject(false);
81 aCustomCoolObject.expressCoolness();
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.
86 In fact, you might be familiar with constructor functions in JS that use the `new` keyword. So what does the `new` keyword do?
88 ## Let’s talk about `new`
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.
94 So we could re-write the previous `createCoolObject` in this format as follows:
97 const CoolObject = function (isCool) {
100 CoolObject.prototype = {isCool: true, expressCoolness: function () { console.log("Am I cool? You know that's " + this.isCool) }};
102 const aConstructedCoolObject = new CoolObject(false);
103 aConstructedCoolObject.expressCoolness();
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.
109 const CoolClass = class {
111 constructor(isCool) {
112 this.isCool = isCool;
118 console.log("Am I cool? You know that's " + this.isCool)
122 const aConstructedCoolObject = new CoolClass(false);
123 aConstructedCoolObject.expressCoolness();
126 These are all equivalent. But there’s no magic going on in the background, just a chain of prototypes.
128 OK. So now we know how we can create objects and link objects to other objects by creating prototype chains.
130 ## Let’s talk about `this`.
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.
136 Let’s look at these examples. What is the outcome of each of them?
141 function aLonelyFunctionDeclaration () {
145 const aLonelyFunctionExpression = function () {
150 aFunctionInAnObject: function() {
155 const anExtractedFunction = myObject.aFunctionInAnObject;
158 aLonelyFunctionDeclaration();
159 aLonelyFunctionExpression();
160 myObject.aFunctionInAnObject();
161 anExtractedFunction();
164 OK. So 3 of them: it was the global object. For 1 of them it was the `myObject` object.
166 Let’s look at this variation.
169 node --use-strict // or 'use strict';
171 function aLonelyFunctionDeclaration () {
175 const aLonelyFunctionExpression = function () {
180 aFunctionInAnObject: function() {
185 const anExtractedFunction = myObject.aFunctionInAnObject;
188 aLonelyFunctionDeclaration();
189 aLonelyFunctionExpression();
190 myObject.aFunctionInAnObject();
191 anExtractedFunction();
195 OK So this gives us some hints. *What can you infer about it?*
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”.
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.
202 That’s pretty much it… well. Almost.
211 const iRunCallbacks = function(callback) { callback(); };
215 console.log("Am I cool? You know that's " + this.isCool)
218 iRunCallbacks(this.expressCoolness);
222 coolObject.callWithCallback();
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.
228 Option 1. Wrap the call in a function.
235 const iRunCallbacks = function(callback) { callback(); };
239 console.log("Am I cool? You know that's " + this.isCool)
243 const myCaller = this;
244 iRunCallbacks(function () {
245 myCaller.expressCoolness()
250 coolObject.callWithCallback();
254 A bit verbose, but it keeps the right caller by keeping a closure to `this` inside the `myCaller` variable.
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
263 const iRunCallbacks = function(callback) { callback(); };
267 console.log("Am I cool? You know that's " + this.isCool)
271 iRunCallbacks(this.expressCoolness.bind(this));
275 coolObject.callWithCallback();
279 There’s also `apply` and `call` which is pretty much the same but it also executes the function (somewhat equivalent to `myFunction.bind(…)()`).
281 ## Let’s talk about arrow functions
282 ES6 introduced some syntax sugar for this purpose in the form of `() =>`
289 const iRunCallbacks = function(callback) { callback(); };
293 console.log("Am I cool? You know that's " + this.isCool)
297 iRunCallbacks(() => this.expressCoolness());
301 coolObject.callWithCallback();
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.
308 Arrow functions get the `this` based on what was the `this` in the scope they were defined.
310 ## Let’s talk about prototypes again
312 The fact that it works like this, means that javascript is extremely powerful!
314 For example, have you ever encountered a statement like this?
316 `Array.prototype.slice.call(arguments)`
318 We’re using a function of the `Array.prototype` object in a non-array-like object. This works
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:
327 return `[${this.called}]`;
331 String.prototype.repeat.call(notAString, 10)
332 String.prototype.repeat.call(notAString, 10)
335 Now you know everything you need to know*
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)
343 ## Let’s talk about the event loop.
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”
347 Every function call that is the root of the stack (ie. it’s not running from inside another function), creates a task.
349 Every function call from inside another function, adds to the stack.
351 Each message will be run *to completion*. That is, no other message will be processed until the previous one is complete.
353 Consider these two functions
355 let someGlobalState = 1;
357 function printAndIncreaseGlobalStateByTen() {
358 console.log("Running some prints with global state", someGlobalState);
359 for (let i = 0; i < 10; ++i) {
360 printANumber(someGlobalState);
363 console.log("Prints run, new global state is", someGlobalState);
366 function printANumber(number) {
368 console.log("here's a number: ", number);
371 function duplicateGlobalState() {
372 someGlobalState = someGlobalState * 2;
373 console.log("Duplicated global state", someGlobalState);
376 printAndIncreaseGlobalStateByTen();
377 duplicateGlobalState();
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`.
383 let someGlobalState = 1;
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)
393 console.log("Prints run, new global state is", someGlobalState);
396 function printANumber(number) {
398 console.log("here's a number: ", number);
401 function duplicateGlobalState() {
402 someGlobalState = someGlobalState * 2;
403 console.log("Duplicated global state", someGlobalState);
406 printAndIncreaseGlobalStateByTen();
407 duplicateGlobalState();
410 What will the outcome of this be?
412 You can compare the two as follows:
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]`
417 Note that `setTimeout` tells you the *minimum* time before the function is executed, and not the actual time.
419 Why is this important? Well, the event loop has other tasks to run while your code is running:
424 * React to user interaction events
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.
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 :(
430 ## Let’s talk about `requestAnimationFrame`
432 For tasks that are tied to updating rendered state, you have another option that is better than `setTimeout`. `requestAnimationFrame`.
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.
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`
439 ## Let’s talk about Microtasks
441 There’s a different queue for “Microtasks”. Microtasks are promise callbacks and callbacks from the mutation observer.
443 Unlike tasks, *all* microtasks are executed every time the queue is empty. This includes microtasks queued as a result of this.
445 This means that loops of microtasks *can block render*. Be careful when looping promise callbacks.
447 https://codepen.io/rbdr/pen/gOWPwzv
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.
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.
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)
459 Or learn about arrays here: [Array - JavaScript | MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)
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` )
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)