├── LICENSE ├── README.md ├── chapters ├── README.md ├── appendix-a.md ├── appendix-b.md ├── appendix-c.md ├── chapter-0.md ├── chapter-1.md ├── chapter-2.md ├── chapter-3.md ├── chapter-4.md ├── chapter-5.md ├── chapter-6.md ├── chapter-7.md └── chapter-8.md └── images ├── car-constructor.drawio ├── car-constructor.png ├── class-func.png ├── cover-big.gif ├── cover-small.gif ├── cover-src.html ├── date-instanceof.drawio ├── date-instanceof.png ├── derived-class.drawio ├── derived-class.png ├── memory-1.png ├── memory-2.png ├── memory.drawio ├── optimized-cover.gif ├── person-class.drawio ├── person-class.png ├── prototype-chain.png ├── prototype-function.png ├── prototype-same.png ├── prototype-unique.png ├── proxy-traps.drawio ├── proxy-traps.png ├── simple-cover.png └── strict-vs-sloppy-mode.png /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Carl Riis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Advanced JavaScript Objects

2 |

An e-book entirely about JavaScript objects

3 | 4 |
5 | Cover 6 |
7 | 8 | ## Chapters 9 | 10 | Total length: 70 A4 pages. 11 | 12 | - **[Chapter 0](./chapters/chapter-0.md) - Introduction** 13 | - **[Chapter 1](./chapters/chapter-1.md) - Getting Our Data Structures Straight** 14 | - **[Chapter 2](./chapters/chapter-2.md) - Object Basics** 15 | - **[Chapter 3](./chapters/chapter-3.md) - Property Descriptors and Object Restrictions** 16 | - **[Chapter 4](./chapters/chapter-4.md) - Internal Object Behavior** 17 | - **[Chapter 5](./chapters/chapter-5.md) - Prototypes** 18 | - **[Chapter 6](./chapters/chapter-6.md) - this** 19 | - **[Chapter 7](./chapters/chapter-7.md) - Constructors** 20 | - **[Chapter 8](./chapters/chapter-8.md) - Classes** 21 | - **[Appendix A](./chapters/appendix-a.md) - Well-known Symbols** 22 | - **[Appendix B](./chapters/appendix-b.md) - Strict Mode** 23 | - **[Appendix C](./chapters/appendix-c.md) - receiver argument for traps** 24 | 25 | ## YouTube video about arrays 26 | 27 | Inspired by this book, I created a YouTube video where I cover the quirks of JavaScript arrays. I also show you *how* I learned it by going over the ECMAScript specification. Watch it by clicking the image below. 28 | 29 | [![Watch the video](https://img.youtube.com/vi/pybgPOnKiY8/mqdefault.jpg)](https://youtu.be/pybgPOnKiY8) 30 | 31 | 32 | ## Other resources 33 | 34 | If you're looking for other resources on JavaScript, here's what I recommend: 35 | 36 | - [javascript.info](https://javascript.info/) - A broad resource for beginners. 37 | - [You Don't know JS](https://github.com/getify/You-Dont-Know-JS) - A book series covering subjects in depth for beginners. 38 | - [2ality.com](https://2ality.com/) - Books and blog posts for both beginners and specification enthusiasts. 39 | - [Understanding ECMAScript 6](https://leanpub.com/understandinges6/read) - A book written when ES6 came out describing its features. 40 | - [The ECMAScript specification](https://tc39.es/ecma262/#sec-intro) - Described in [Chapter 0](./chapters/chapter-0.md). 41 | 42 | ## License 43 | 44 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. 45 | All images were created by me. Feel free to steal them (same goes for everything else of course). 46 | -------------------------------------------------------------------------------- /chapters/README.md: -------------------------------------------------------------------------------- 1 | ## Chapters 2 | 3 | 4 | 5 | - **[Chapter 0](./chapter-0.md) - Introduction** 6 | - **[Chapter 1](./chapter-1.md) - Getting Our Data Structures Straight** 7 | - **[Chapter 2](./chapter-2.md) - Object Basics** 8 | - **[Chapter 3](./chapter-3.md) - Property Descriptors and Object Restrictions** 9 | - **[Chapter 4](./chapter-4.md) - Internal Object Behavior** 10 | - **[Chapter 5](./chapter-5.md) - Prototypes** 11 | - **[Chapter 6](./chapter-6.md) - this** 12 | - **[Chapter 7](./chapter-7.md) - Constructors** 13 | - **[Chapter 8](./chapter-8.md) - Classes** 14 | - **[Appendix A](./appendix-a.md) - Well-known Symbols** 15 | - **[Appendix B](./appendix-b.md) - Strict Mode** 16 | - **[Appendix C](./appendix-c.md) - receiver argument for traps** -------------------------------------------------------------------------------- /chapters/appendix-a.md: -------------------------------------------------------------------------------- 1 | # Appendix A - Well-known Symbols 2 | 3 | Symbols can even be used to expose the internal logic of JavaScript. There is a set of predefined symbols called *well-known Symbols.* These predefined symbols live as properties on objects and represent functionality that was previously only accessible by internal algorithms in JavaScript. These well-known Symbols can be found as properties on the `Symbol` object. Here they are listed: 4 | 5 | - `Symbol.asyncIterator` 6 | - `Symbol.hasInstance` 7 | - `Symbol.isConcatSpreadable` 8 | - `Symbol.iterator` 9 | - `Symbol.match` 10 | - `Symbol.matchAll` 11 | - `Symbol.replace` 12 | - `Symbol.search` 13 | - `Symbol.species` 14 | - `Symbol.split` 15 | - `Symbol.toPrimitive` 16 | - `Symbol.toStringTag` 17 | - `Symbol.unscopables` 18 | 19 | ### Well-known symbol example 20 | 21 | An interesting well-known Symbol is `Symbol.toStringTag`. It is used to give a default description of an object. It is used by `Object.prototype.toString`, which is the method called when you execute `.toString()` on your object literal. 22 | By setting a custom value on the `Symbol.toStringTag` property, we can trick `Object.prototype.toString` into labeling our object whatever we want. 23 | 24 | ```js 25 | const obj = {} 26 | 27 | console.log(obj.toString()) // [object Object] 28 | 29 | obj[Symbol.toStringTag] = "Hi Mom!" 30 | 31 | console.log(obj.toString()) // [object Hi Mom!] 32 | ``` 33 | -------------------------------------------------------------------------------- /chapters/appendix-b.md: -------------------------------------------------------------------------------- 1 | # Appendix B - Strict Mode 2 | 3 | Strict mode is something you can enable in your JavaScript code. Running your code in strict mode will result in a slightly different interpretation of the language. Strict mode is described as a more "modern version" of JavaScript that is less prone to errors. 4 | 5 | Not running your code in strict mode is sometimes referred to as "Sloppy mode". 6 | 7 | ![Strict vs sloppy mode goofy image](../images/strict-vs-sloppy-mode.png) 8 | 9 | ## When is my code running in strict mode? 10 | 11 | One way is to put a `“use strict”` statement at the top of a file. 12 | 13 | ```js 14 | "use strict" 15 | 16 | console.log("This is running in strict mode!") 17 | ``` 18 | 19 | It can also be enabled for an individual function. 20 | 21 | ```js 22 | function func() { 23 | "use strict" 24 | console.log("This is running in strict mode!") 25 | } 26 | ``` 27 | 28 | Strict mode is also auto-enabled for all [ES modules](https://hacks.mozilla.org/2018/03/es-modules-a-cartoon-deep-dive/). You are using ES modules if you use the import/export keywords. 29 | 30 | ```js 31 | import {} from "./example" 32 | 33 | console.log("This is running in strict mode!") 34 | 35 | export {} 36 | ``` 37 | 38 | ## Example of difference 39 | 40 | An example can be found in [chapter 6 "Traditional function on its own"](./chapter-6.md#traditional-function-on-its-own). 41 | 42 | 43 | -------------------------------------------------------------------------------- /chapters/appendix-c.md: -------------------------------------------------------------------------------- 1 | # Appendix C - receiver argument for traps 2 | 3 | The `receiver` argument becomes important if you want a trap that returns the same value it would without the trap, e.g, a [[Get]] trap only used for logging purposes. You would expect to be able to just return `target[key]`, but that doesn’t always work. It might not work if your proxy becomes part of a prototype chain. 4 | 5 | Consider the following example. Here we have a person object that combines `this.name` with `this.lastName` in the getter method `fullName`. The Object `paul` inherits this method. This works like a charm. 6 | 7 | ```js 8 | const person = { 9 | get fullName() { 10 | return `${this.name} ${this.lastName}` 11 | } 12 | } 13 | 14 | const paul = { name: "Paul", lastName: "Bertsen" } 15 | Reflect.setPrototypeOf(paul, proxyPerson) 16 | 17 | console.log(paul.fullName) // Paul Bertsen 18 | ``` 19 | 20 | But what if we want to log something every time a property is read on `person`? We could wrap it in a proxy, do our logging in the [[Get]] trap, and return `target[key]`, right? No, this wouldn’t work. 21 | 22 | ```js 23 | const person = { 24 | get fullName() { 25 | return `${this.name} ${this.lastName}` 26 | } 27 | } 28 | 29 | const proxyPerson = new Proxy(person, { 30 | get: (target, key, receiver) => { 31 | // Logging here ... 32 | return target[key] 33 | } 34 | }) 35 | 36 | const paul = { name: "Paul", lastName: "Bertsen" } 37 | Reflect.setPrototypeOf(paul, proxyPerson) 38 | 39 | console.log(paul.fullName) // undefined undefined 40 | ``` 41 | 42 | Here, it’s important to understand the difference between `target` and `receiver`. 43 | 44 | - `receiver` in this case is `paul` because we initially read `fullName` on `paul`. 45 | - `target` in this case is `person` because we specified it as the target when we created the `proxyPerson` proxy. 46 | 47 | Getting `target[key]` becomes the same as getting `person[key]`. We are missing `paul`. 48 | 49 | Maybe we should get `receiver[key]` instead since `receiver` is `paul`? No, this would create an infinite loop because the prototype of `paul` is our proxy. It would look something like this: 50 | 51 | - Get `paul.fullName`. It doesn’t exist. 52 | - The prototype of `paul` is `proxyPerson`. Try getting `proxyPerson.fullName` instead. 53 | - The [[Get]] trap on `proxyPerson` wants to get `paul.fullName`. 54 | - Get `paul.fullName`. It doesn’t exist. 55 | - ... 56 | 57 | What we need, is a way to get `target[key]` but supply `receiver` as the custom `this` for any possible getter function. Meaning, we call the getter for `person.fullName` with `this` being `paul`. That would work. Perhaps we could extract the property’s getter function from its descriptor and call it using `bind`, `call`, or `apply`. There is an easier way. We can use `Reflect.get`. Its last argument (conveniently also named `receiver`) will become `this` in any getter/setter function. It therefor achieves exactly what we need. 58 | 59 | ```js 60 | const person = { 61 | get fullName() { 62 | return `${this.name} ${this.lastName}` 63 | } 64 | } 65 | 66 | const proxyPerson = new Proxy(person, { 67 | get: (target, key, receiver) => { 68 | // Logging here ... 69 | return Reflect.get(target, key, receiver) 70 | } 71 | }) 72 | 73 | const paul = { name: "Paul", lastName: "Bertsen" } 74 | Reflect.setPrototypeOf(paul, proxyPerson) 75 | 76 | console.log(paul.fullName) // Paul Bertsen 77 | ``` 78 | 79 | >💡 The above example explained the `receiver` argument in the context of a [[Get]] trap. However, it is also relevant for a [[Set]] trap. That, too, can get passed as a `receiver` argument, and its corresponding `Reflect.set` function also takes in a `receiver` argument. 80 | > 81 | -------------------------------------------------------------------------------- /chapters/chapter-0.md: -------------------------------------------------------------------------------- 1 | # Chapter 0 - Introduction 2 | 3 | The main resource used to write this book was the [ECMAScript specification](https://tc39.es/ecma262/), which is the specification that JavaScript adheres to. It describes in detail how the language should behave. As a learning resource, it's fantastic, although very dense and long (around 1300 pages). I spent a lot of time studying the specification and decided to write a shorter, more intuitive resource. Since I didn't have time to do this for the entire language, I picked the most interesting part to me, objects. 4 | 5 | ## Prerequisites 6 | 7 | I like to think of this book as a resource that can prepare you for reading the EcmaScript specification yourself. Meaning, I try not to use too much abstraction. That being said, I don't think you need to be an advanced JavaScript programmer to understand it. Beginner/intermediate JavaScript programmers should be able to follow along just fine. 8 | 9 | This book won't go deeply into the syntax of objects. If you want to brush up on that, I recommend reading https://javascript.info/object. 10 | 11 | ## Reading sequence 12 | 13 | You will get the most value out of this book by reading it chronologically. Most chapters depend on the chapters before them. Despite that, it shouldn't be too confusing to jump into a random chapter as long as you have a basic understanding of the subjects covered before it. 14 | 15 | ## Conventions used 16 | 17 | The following typographical conventions are used: 18 | 19 | - *Italics* is used for new terms and emphasis. 20 | - `Constant width` indicates a piece of code. 21 | 22 | Additionally, longer code examples are contained in code blocks such as: 23 | 24 | ```js 25 | function doSomething() { 26 | // empty 27 | } 28 | ``` 29 | 30 | Things of note will be presented like this: 31 | 32 | > 💡 This is an idea block. It will contain useful information that isn't necessarily part of the main text. 33 | > 34 | 35 | > ⚠️ This is a warning block. It will contain information that prevents you from making mistakes. 36 | > 37 | 38 | ## Footnotes 39 | 40 | Footnotes are sprinkled throughout this book. It is not required to read these. They often link to external resources or note things of little significance to most readers. 41 | 42 | Many footnotes link to places in the ECMAScript specification. I encourage you to try exploring these if it interests you. 43 | 44 | ## Issues 45 | 46 | Need help with an example? Found a mistake? Please feel free to open an issue here https://github.com/carltheperson/advanced-js-objects 47 | 48 |
49 | 50 | Next chapter: [Chapter 1 - Getting Our Data Structures Straight ➡️](./chapter-1.md) 51 | -------------------------------------------------------------------------------- /chapters/chapter-1.md: -------------------------------------------------------------------------------- 1 | # Chapter 1 - Getting Our Data Structures Straight 2 | 3 | To understand objects, it’s essential to understand where objects fit into the JavaScript data structure scene. This chapter first covers the different JavaScript types and then covers which ones contain so-called *primitives*. I will also attempt to clear some confusion regarding the `typeof` operator. 4 | 5 | ## What is a type? 6 | 7 | Every value you define will have a type. Here are all JavaScript types[^spec-types]: 8 | 9 | - String 10 | - Number 11 | - BigInt 12 | - Boolean 13 | - Symbol 14 | - Undefined 15 | - Null 16 | - Object 17 | 18 | Usually, you don't have to think too much about types in JavaScript. That's because types are inferred automatically from values. But being able to categorize your values into types is still quite helpful. As you'll see, different types have very different capabilities. 19 | 20 | The main thing to know about types is that they group values. Some types contain more values than others, and some contain only one value. But it's important to always think of them as sets of values. All the types listed above will be further explained below. 21 | 22 | > 💡 This book will describe the name of a type using capitalization e.g. "Object" or "Number" [^capitalization]. However, when types aren't explicitly addressed, they will be described in lower-case, e.g. "object(s)" or "number(s)". 23 | > 24 | 25 | ### String 26 | 27 | Text values are of the String type. 28 | 29 | ```js 30 | const hello = "Hello world!" // Type is String 31 | ``` 32 | 33 | In the above example, the value is `“Hello world!”` and the type of the value is String. 34 | 35 | You can imagine that the range of possible values for the String type is quite large, which is true. The number of different String combinations is practically infinite. 36 | 37 | ### Number 38 | 39 | There exist two types for numeric values in JavaScript. The most common is Number. It consists of 18,437,736,874,454,810,624 numeric values, half of which are positive, and the other half are negative[^number-amount]. 40 | 41 | ```js 42 | // Type is Number 43 | const num1 = -25` 44 | const num2 = 0 45 | const num3 = 524.75 46 | ``` 47 | 48 | The Number type also includes three special values. They are Not-a-Number, positive Infinity, and negative Infinity. They are represented as `NaN`, `+Infinity`, and `-Infinity`. 49 | 50 | ```js 51 | // Type is Number 52 | const num1 = NaN 53 | const num2 = +Infinity 54 | const num3 = -Infinity 55 | ``` 56 | 57 | ### BigInt 58 | 59 | BigInt is an alternative type for a numeric value. It differs from Number by allowing numeric values of any size. This becomes useful if you’re performing mathematical operations on very large numbers. 60 | 61 | You define a value to be of type BigInt by appending an `n` to your numbers. 62 | 63 | ```js 64 | const num = 100n 65 | ``` 66 | 67 | You can’t represent decimals with BigInts. 68 | 69 | ```js 70 | const num = 100.5n // SyntaxError: unexpected token 71 | ``` 72 | 73 | You also can’t mix BigInt values with values of other types. 74 | 75 | ```js 76 | const num = 100n + 100 // TypeError: Cannot mix BigInt and other types 77 | ``` 78 | 79 | ### Boolean 80 | 81 | The Boolean type contains only the values `true` and `false`. 82 | 83 | ```js 84 | // Type is Boolean 85 | const bool1 = true 86 | const bool2 = false 87 | ``` 88 | 89 | ### Symbol 90 | 91 | Symbols allow you to create your own custom unique values. You create a new symbol with the built-in `Symbol` function[^symbol-function]. 92 | 93 | ```js 94 | const mySymbol = Symbol() // Type is Symbol 95 | ``` 96 | 97 | You can optionally give your symbols a description. This is mostly to act as documentation. 98 | 99 | ```js 100 | const mySymbol = Symbol("This is my Symbol") 101 | ``` 102 | 103 | Interestingly, symbols can be used as keys for object properties. More on that later. 104 | 105 | ### Undefined and Null 106 | 107 | The Undefined and Null types are a bit special. They each contain just one value. Undefined contains only the value `undefined`, and Null contains only the value `null`. 108 | 109 | ```js 110 | const a = undefined // Type is Undefined 111 | const b = null // Type is Null 112 | ``` 113 | 114 | ### Object 115 | 116 | Objects are collections of properties. These properties map keys to values. 117 | 118 | ```js 119 | const obj = { 120 | key: "Value" 121 | } 122 | ``` 123 | 124 | ## What is a primitive? 125 | 126 | Certain types contain values that are *primitive*. Primitive values belong to the following types: 127 | 128 | - String 129 | - Number 130 | - BigInt 131 | - Boolean 132 | - Symbol 133 | - Undefined 134 | - Null 135 | 136 | The only type that doesn’t contain primitive values is Object. This means that all objects are non-primitive values. 137 | 138 | Primitives are represented directly at the lowest level of the language implementation. All primitives are *immutable*, meaning they can’t be changed after creation. Have you ever noticed how it’s impossible to change a string after creating it? 139 | 140 | ```js 141 | const myString = "Hello World" 142 | myString.repeat(5) 143 | console.log(myString) // Hello World 144 | ``` 145 | 146 | The value of `myString` isn’t repeated 5 times as you might expect. The `repeat` method actually returns a new string because it can’t modify the primitive `“Hello World”` value. 147 | 148 | Here is another example where we try to change the first letter to `A`. 149 | 150 | ```js 151 | const myString = "Hello World" 152 | myString[0] = "A" 153 | console.log(myString) // Hello World 154 | ``` 155 | 156 | As you can see, this is not possible. 157 | 158 | > 💡 If we declare `myString` with `let`, we can do the following: 159 | > ```js 160 | > let myString = "Hello World 1" 161 | > myString = "Hello World 2" 162 | > console.log(myString) // Hello World 2 163 | > ``` 164 | > This looks like we changed the value of `myString`, but we didn't. We assigned it to a *new* value. It's important to note that the *variable* `myString` is not necessarily immutable. It's the *value* of `myString` that is immutable e.g. `"Hello World"`. 165 | > 166 | 167 | ### Primitive wrapper objects 168 | 169 | Another quirk of primitives is that they can’t have properties/methods. This might be surprising to learn, after all, the above example uses a *method* called `repeat`. It’s true that `repeat` is a method, but it doesn’t exist on the primitive itself. The method exists on a *wrapper object*[^wrapping]. 170 | All primitives except Undefined and Null have wrapper objects. When you attempt to access a property on a primitive, a wrapper object is instantiated, which you access instead. The wrapper object for String types is called `String`. `String` is a globally available constructor, which means we can easily simulate the work JavaScript does under the hood. 171 | 172 | ```js 173 | const a1 = "a".repeat(5) 174 | const a2 = new String("a").repeat(5) 175 | console.log(a1) // aaaaa 176 | console.log(a2) // aaaaa 177 | ``` 178 | 179 | In the above example, we let JavaScript create the wrapper object for `a1`, but we do it ourselves for `a2`. The result is the same. 180 | 181 | Interestingly, the specific instance of the wrapper object only exists when you try to access a property and is discarded right after. That’s why this code doesn’t work: 182 | 183 | ```js 184 | const myString = "Hello World" 185 | myString.someField = "Can you see me?" 186 | console.log(myString.someField) // undefined 187 | ``` 188 | 189 | We set `someField` on a `String` wrapper object that then is immediately discarded, meaning we receive another instance of `String` on the last line. 190 | 191 | ## typeof 192 | 193 | I feel it’s necessary to explain some confusing parts of the `typeof` operator. 194 | 195 | Using the operator on a value produces a string that gives an indication of the type. Here are the possible values it can return: 196 | 197 | - `"string"` 198 | - `"number"` 199 | - `"bigint"` 200 | - `"boolean"` 201 | - `"symbol"` 202 | - `"undefined"` 203 | - `"object"` 204 | - `"function"` 205 | 206 | First, notice how all the types are represented in lower-case. Second, notice the lack of the Null type. This is what happens if you use the `typeof` operator on `null`: 207 | 208 | ```js 209 | console.log(typeof null) // object 210 | ``` 211 | 212 | The type of `null` is wrongly computed to be Object. This is a bug that was introduced in the first implementation of JavaScript[^typeof]. It has never been possible to correct the bug since a lot of programs depend on the false result. If you need a more accurate way to check for objects, you have to do the following. 213 | 214 | ```js 215 | if (typeof value === "object" && value !== null) { 216 | console.log("Is object") 217 | } 218 | ``` 219 | 220 | The last notable thing about the `typeof` operator is how it differentiates between objects and functions. Functions in JavaScript don’t have their own type and instead belong to the Object type. This is because functions are just callable Objects. So it’s reasonable to expect the `typeof` operator to return `“object”` for a function, but it instead returns `“function”`. 221 | 222 | ```js 223 | const func = () => {} 224 | console.log(typeof func) // function 225 | ``` 226 | 227 |
228 | 229 | Next chapter: [Chapter 2 - Object Basics ➡️](./chapter-2.md) 230 | 231 | [^capitalization]: This is the same capitalization that the specification uses https://tc39.es/ecma262/#sec-ecmascript-language-types. 232 | [^spec-types]: Taken, in same order, from here https://tc39.es/ecma262/#sec-ecmascript-language-types. 233 | [^number-amount]: https://tc39.es/ecma262/#sec-ecmascript-language-types-number-type 234 | [^symbol-function]: Some people refer to this as a constructor which is not accurate. It's a constructor in a conceptual sense because it lets you create new symbols, but it doesn't have an internal [[Construct]] method meaning it will throw an error when used with `new`. 235 | [^wrapping]: This is how it happens in the specification: 236 | - A property access https://tc39.es/ecma262/#sec-property-accessors-runtime-semantics-evaluation 237 | - Calls GetValue https://tc39.es/ecma262/#sec-getvalue 238 | - Which calls ToObject https://tc39.es/ecma262/#sec-toobject (Creates the wrapper object) 239 | 240 | I recommend reading this great post https://2ality.com/2022/03/properties-of-primitives.html 241 | [^typeof]: A good read on this https://2ality.com/2013/10/typeof-null.html -------------------------------------------------------------------------------- /chapters/chapter-2.md: -------------------------------------------------------------------------------- 1 | # Chapter 2 - Object Basics 2 | 3 | An object is a collection of properties. A property is the mapping between a key and a value. 4 | 5 | ```js 6 | const obj = { 7 | "abc": 100 8 | } 9 | ``` 10 | 11 | In the above example, we have an object called `obj`. It has a property with the key `“abc”` and the value `100`. 12 | 13 | The specific syntax we used to create the above object makes it an *object literal.* Object literals are defined using curly braces (`{}`) that enclose properties. The properties are defined using the colon (`:`), which separates the key and value. Object literals are not the only way to create objects. More on that later [^object-creation]. 14 | 15 | ### Property key types 16 | 17 | A property key can only be of type String or Symbol. It is possible to *define* a property using a key that is not one of those types, but this will internally convert the key to a string. Below, we define a property key with the number `100`. When we retrieve the keys for the object, the type of our key has changed to String, giving us `“100”`. 18 | 19 | ```js 20 | const obj = { 100: true } 21 | console.log(Object.keys(obj)) // [ "100" ] 22 | ``` 23 | 24 | JavaScript internally recognized that `100` isn't a string or symbol, so it converted it to a string before making the assignment[^key-conversion]. This behavior can also be seen with other types. 25 | 26 | ```js 27 | const obj = { [true]: "", [undefined]: "", [null]: "", [{}]: "" } 28 | console.log(Object.keys(obj)) // [ "true", "undefined", "null", "[object Object]" ] 29 | ``` 30 | 31 | The same possible conversion that happens when you define a property can also happen when you retrieve a property value. 32 | 33 | ```js 34 | const obj = { [true]: "a", 100: "b" } 35 | 36 | console.log(obj["true"]) // a 37 | console.log(obj["100"]) // b 38 | ``` 39 | 40 | >💡 This behavior becomes very clear with arrays. Arrays are objects like object literals, meaning their keys are strings. Array object keys are usually referred to as *indexes*. 41 | >```js 42 | >const arr = ["a", "b", "c"] 43 | >console.log(Object.keys(arr)) // [ "0", "1", "2" ] 44 | >``` 45 | > 46 | 47 | ### Symbol property keys 48 | 49 | As mentioned before, symbols are also valid object keys. This means that they don’t get converted into strings when used as property keys. 50 | 51 | ```js 52 | const key1 = "Key 1"; 53 | const key2 = Symbol("Key 2"); 54 | 55 | const obj = { 56 | [key1]: "Value 1", 57 | [key2]: "Value 2" 58 | } 59 | 60 | console.log(obj[key1]) // Value 1 61 | console.log(obj[key2]) // Value 2 62 | ``` 63 | 64 | An advantage of using symbols as keys is that they are guaranteed to be unique. This is useful if you want to prevent properties from being overridden by code consuming your objects. Also, symbols aren’t returned by the conventional methods used to retrieve property keys like `Object.keys` and `Object.getOwnPropertyNames`. 65 | 66 | ```js 67 | const obj = { [Symbol("Key 1")]: true, "Key 2": true } 68 | 69 | console.log(Object.keys(obj)) // [ "Key 2" ] 70 | console.log(Object.getOwnPropertyNames(obj)) // [ "Key 2" ] 71 | ``` 72 | 73 | If you really want to retrieve symbol property keys you can use `Object.getOwnPropertySymbols` or `Reflect.ownKeys`. The first returns only symbol keys and the second returns all property keys regardless of their type. 74 | 75 | ```js 76 | const obj = { [Symbol("Key 1")]: true, "Key 2": true } 77 | 78 | console.log(Object.getOwnPropertySymbols(obj)) // [ Symbol(Key 1) ] 79 | 80 | console.log(Reflect.ownKeys(obj)) // [ Symbol(Key 1), "Key 2" ] 81 | ``` 82 | 83 | ### Well-known Symbols 84 | 85 | Some symbols are considered special to the internals of JavaScript. They are known as the *Well-known Symbols*[^well-known-symbols]. Using them to define property keys can alter internal functionality. You can read more about them in [Appendix A](./appendix-a.md). 86 | 87 | ### Property value types 88 | 89 | If property keys are restricted to only strings and symbols, how are property values restricted? The answer is that they aren’t. Values of properties can be of any type, even other objects. 90 | 91 | ```js 92 | const obj = { 93 | key1: "Value 1", 94 | key2: 200, 95 | key3: 300n, 96 | key4: true, 97 | key5: null, 98 | key6: undefined, 99 | key7: Symbol(), 100 | key8: { isObject: true } 101 | } 102 | ``` 103 | 104 | ### Primitives vs object values/references 105 | 106 | Primitives and objects behave differently when assigned to variables or passed to functions. When you assign a primitive to a variable, the value is stored directly in the memory address associated with that variable. When you assign an object to a variable, a *reference* to the object is stored instead. 107 | 108 | ```js 109 | const value1 = 10 110 | const value2 = { isObj: true } 111 | ``` 112 | 113 | Below is an illustration of the above code. It shows `value1` directly storing `10` and `value2` storing a reference (`0x02`) to the `{isObj:true}` object. 114 | 115 | ![Memory addresses 1](../images/memory-1.png) 116 | 117 | When copying primitive values into other variables, a simple copy is made of the value. When copying objects, you instead copy the reference. This means multiple variables can reference the same object value. 118 | 119 | ```js 120 | let obj1 = { changed: false } 121 | let obj2 = obj1 122 | 123 | obj2.changed = true 124 | 125 | console.log(obj1) // { changed: true } 126 | console.log(obj2) // { changed: true } 127 | ``` 128 | 129 | Above, we create an object with a reference assigned to the variable `obj1`. We then copy the reference into the variable `obj2`. When then change a property on the object that `obj2` references. This is, of course, the same object that `obj1` references. They effectively share an object. 130 | 131 | ![Memory addresses 2](../images/memory-2.png) 132 | 133 | Note, that there is a difference between changing an object and assigning a brand new object to a variable. Below, we re-assign the variable `obj2` to a new object literal. The result is that the variables reference two separate objects. 134 | 135 | ```js 136 | let obj1 = { changed: false } 137 | let obj2 = obj1 138 | 139 | obj2 = { changed: true } // <-- New Object! 140 | 141 | console.log(obj1) // { changed: false } 142 | console.log(obj2) // { changed: true } 143 | ``` 144 | 145 | > ⚠️ The above illustrations of memory are greatly simplified. The mental model they provide still holds though. 146 | > 147 | 148 | #### Function parameters 149 | 150 | An assignment similar to that of variables happens to function parameters. Consider this example: 151 | 152 | ```js 153 | function func(param) { /* ... */ } 154 | 155 | const myValue = 10 156 | 157 | func(myValue) 158 | ``` 159 | 160 | Here we have a function called `func` and a variable called `myValue`, which holds a primitive value (`10`). We call the function and pass in `myValue` as a parameter. Interestingly, the value of `myValue` is not passed directly. Instead, a copy is made, which is assigned to `param`. This means we can safely assume that functions won’t modify our primitive variables. 161 | 162 | ```js 163 | function iCantChangeYourPrimitive(val) { 164 | val = 200 165 | } 166 | 167 | const myVal = 100 168 | iCantChangeYourPrimitive(myVal) 169 | console.log(myVal) // 100 170 | ``` 171 | 172 | The same safety cannot be assumed for objects passed to functions. We have to remember that we are assigning a *reference* to the parameter. This means that the function doesn’t get a copy of the object, it gets a copy of the reference. 173 | 174 | ```js 175 | function iCanChangeYourObject(obj) { 176 | obj.changed = true 177 | } 178 | 179 | const myObj = {} 180 | iCanChangeYourObject(myObj) 181 | console.log(myObj) // { changed: true } 182 | ``` 183 | 184 | #### Object assigned to other object's property 185 | 186 | What happens to objects when you assign them to other objects as properties? The answer is that the object *reference* becomes the property value. This is the same functionality we have with variable and function parameter assignment. 187 | 188 | ```js 189 | const obj1 = { changeMe: "" } 190 | const obj2 = { obj1Here: obj1 } 191 | 192 | obj1.changeMe = "I changed!" 193 | 194 | console.log(obj2.obj1Here) // { changeMe: "I changed!" } 195 | ``` 196 | 197 | This can, in some rare cases, lead to a circular object structure where two objects both reference *each other*. This becomes a problem if you want to convert the objects to a format like JSON, which doesn’t support references. 198 | 199 | ```js 200 | const obj1 = {} 201 | const obj2 = {} 202 | 203 | obj1.otherObj = obj2 204 | obj2.otherObj = obj1 205 | 206 | // TypeError: Converting circular structure to JSON 207 | console.log(JSON.stringify(obj1)) 208 | ``` 209 | 210 |
211 | 212 | Next chapter: [Chapter 3 - Property Descriptors and Object Restrictions ➡️](./chapter-3.md) 213 | 214 | [^object-creation]: Another way is using the `Object` constructor. This is mentioned in a note in [Chapter 7](./chapter-7.md#putting-this-and-prototype-together-creating-methods) 215 | [^key-conversion]: https://tc39.es/ecma262/#sec-topropertykey 216 | [^well-known-symbols]: https://tc39.es/ecma262/#sec-well-known-symbols -------------------------------------------------------------------------------- /chapters/chapter-3.md: -------------------------------------------------------------------------------- 1 | # Chapter 3 - Property Descriptors and Object Restrictions 2 | 3 | This chapter will cover ways that you can restrict or alter the functionality of objects. This can be done in two ways. The first is using *descriptors* which affect individual properties. The second is using one of the three methods that restrict entire objects. Both of these ways will be covered below. 4 | 5 | # Property descriptors 6 | 7 | So far, properties have been described as being quite simple. A property is just a key and a value. There is one more thing that determines the functionality of properties, their descriptor. The descriptor for a property is a set of *attributes* that define how a property may be used. Think of it as the configuration for the property. 8 | 9 | You can retrieve the descriptor for a property with `Object.getOwnPropertyDescriptor`. The first argument is the object, and the second is the property key. Here is what the descriptor for `JSON.stringify` looks like: 10 | 11 | ```js 12 | console.log(Object.getOwnPropertyDescriptor(JSON, "stringify")) 13 | /* { 14 | value: [Function: stringify], 15 | writable: true, 16 | enumerable: false, 17 | configurable: true 18 | } */ 19 | ``` 20 | 21 | As you can see, the descriptor is presented as an object with a few properties. Each of these properties represents some functionality associated with `JSON.stringify`. An interesting one is `value`. It literally represents the value of `JSON.stringify`. As we can see, the value of `JSON.stringify` is a function. All the different descriptor attributes will be covered in a bit. 22 | 23 | You can define a property with a custom descriptor using `Object.defineProperty`. It takes in three arguments. The first argument is the object, the second is the property key, and the third is the actual descriptor. 24 | 25 | ```js 26 | const obj = {} 27 | 28 | Object.defineProperty(obj, "myKey", { 29 | value: "Hello!", 30 | writable: true, 31 | enumerable: true, 32 | configurable: true 33 | }) 34 | 35 | console.log(obj) // { myKey: "Hello!" } 36 | ``` 37 | 38 | > 💡 Descriptors are managed by the internals of JavaScript. They are not actually real objects, but are instead kept in a more primitive internal structure[^descriptor-type]. However, when we interact with descriptors, they are converted to objects. This gives them a familiar form. For the sake of simplicity, assume that descriptors are objects. 39 | > 40 | > This also explains why descriptor members are called *attributes* and not properties. They are represented to us as object properties but aren’t actually kept as such. 41 | > 42 | 43 | ## Descriptor attributes 44 | 45 | Here are the six different descriptor attributes with their default value[^descriptor-table]: 46 | 47 | | Attribute name | Default value 48 | | --- | --- | 49 | | `value` | `undefined` | 50 | | `writable` | `false` | 51 | | `enumerable` | `false` | 52 | | `configurable` | `false` | 53 | | `get` | `undefined` | 54 | | `set` | `undefined` | 55 | 56 | ### value 57 | 58 | The `value` attribute, if defined, represents the actual value of the property. 59 | 60 | ```js 61 | const obj = { myKey: 10 } 62 | const desc = Object.getOwnPropertyDescriptor(obj, "myKey") 63 | console.log(desc.value) // 10 64 | ``` 65 | 66 | > 💡 The `value` attribute won't represent the value of your property if it's an *accessor property*. Accessor properties will instead use the `get` attribute. This will be covered later. 67 | > 68 | 69 | 70 | ### writable 71 | 72 | `writable` determines if the property should be able to be re-assigned. Setting it to `false` effectively freezes the initial value and makes the property read-only. 73 | 74 | An attempt to re-assign the read-only property can lead to different outcomes. One possibility is that nothing happens, and the property remains unchanged. The re-assignment just silently fails. This will happen if your code is not running in strict mode. 75 | 76 | > 💡 Strict mode is something that you can enable, which changes the execution of your JavaScript code. You can read more about it in [Appendix B](./appendix-b.md). 77 | > 78 | 79 | Below is a non-strict example: 80 | 81 | ```js 82 | const obj = {} 83 | 84 | Object.defineProperty(obj, "prop", { 85 | value: "Old value", 86 | writable: false 87 | }) 88 | 89 | obj.prop = "New value?" 90 | 91 | console.log(obj.prop) // Old value 92 | ``` 93 | 94 | If you attempt to re-assign a read-only property in strict mode, an error will be thrown. Here is the same re-assignment as above but in strict mode: 95 | 96 | ```js 97 | "use strict" 98 | 99 | // ... 100 | 101 | obj.prop = "New value?" // TypeError: Cannot assign to read only property 102 | ``` 103 | 104 | > ⚠️ While it is not possible to re-assign a read-only property with the conventional assignment operator (`x = val`), it might still be possible to change the property’s entire descriptor if the `configurable` attribute is set to `true`. This could result in a change of value since a different descriptor could contain a different `value` attribute. 105 | > 106 | 107 | ### enumerable 108 | 109 | Setting the `enumerable` attribute to `false` can “hide” your properties in certain contexts. 110 | 111 | Throughout the following examples, we will be using the same object called `obj`. It has three properties, `a`, `b`, and `c`. 112 | 113 | `a` and `c` are *enumerable*, that is, their `enumerable` attribute is set to `true`. 114 | 115 | `b` is not enumerable. 116 | 117 | ```js 118 | const obj = {} 119 | 120 | Object.defineProperty(obj, "a", { value: "A", enumerable: true }) 121 | Object.defineProperty(obj, "b", { value: "B", enumerable: false }) 122 | Object.defineProperty(obj, "c", { value: "C", enumerable: true }) 123 | ``` 124 | 125 | Spread operator 126 | 127 | The spread operator is a way to combine objects by *spreading* an object into another. However, the spread operator will only copy properties that are enumerable. 128 | 129 | ```js 130 | const newObj = { ...obj } 131 | console.log(Object.getOwnPropertyNames(newObj)) // [ "a", "c" ] 132 | ``` 133 | 134 | Retrieving an array of property keys 135 | 136 | Normally, when you want to get an array of property keys for an object, you use `Object.keys`. This, like the spread operator, will only give you enumerable property keys. If you want to include non-enumerable property keys you can use `Object.getOwnPropertyNames`. 137 | 138 | ```js 139 | console.log(Object.keys(obj)) // [ "a", "c" ] 140 | console.log(Object.getOwnPropertyNames(obj)) // [ "a", "b", "c"" ] 141 | ``` 142 | 143 | > ⚠️ None of these methods return properties with symbol keys. If you want *all* property keys for an object, you can use `Reflect.ownKeys`. 144 | > 145 | 146 | console.log 147 | 148 | In NodeJS and Deno, logging an object with `console.log` will only show enumerable properties. 149 | 150 | ```js 151 | console.log(obj) // { a: "A", c: "C" } 152 | ``` 153 | 154 | for...in loop 155 | 156 | The for...in loop gives you a convenient way to iterate over property values. It ignores non-enumerable and symbol properties. 157 | 158 | ```js 159 | for (const value in obj) { 160 | console.log(value, "is enumerable!") 161 | } 162 | // A is enumerable! 163 | // C is enumerable! 164 | ``` 165 | 166 | > ⚠️ The for...in loop will also include enumerable properties from the *prototype chain* of the object. The prototypes chain will be covered in [Chapter 5](./chapter-5.md), but it’s basically a way to set up inheritance between objects. These inherited properties will also be included in the for...in loop if they are enumerable. 167 | > 168 | 169 | Checking if a property is enumerable 170 | 171 | If you want a simple way to check if a property is enumerable, you can use `propertyIsEnumerable`. You call this method on the object itself. 172 | 173 | ```js 174 | console.log(obj.propertyIsEnumerable("a")) // true 175 | console.log(obj.propertyIsEnumerable("b")) // false 176 | ``` 177 | 178 | ### configurable 179 | 180 | Setting the `configurable` attribute to `false` prevents your property from being deleted or having its descriptor changed. 181 | 182 | delete operator 183 | 184 | The delete operator will is used to remove properties from objects. You can only use it to remove configurable properties. If you’re in strict mode and attempt to delete a non-configurable property, an error will be thrown. If you’re not in strict mode, the deletion just silently fails. 185 | 186 | ```js 187 | const obj = {} 188 | 189 | Object.defineProperty(obj, "a", { 190 | value: "A", 191 | enumerable: true, 192 | configurable: false 193 | }) 194 | 195 | delete obj.a 196 | 197 | console.log(obj) // { a: "A" } 198 | ``` 199 | 200 | ```js 201 | "use strict" 202 | 203 | // ... 204 | 205 | delete obj.a // TypeError: Cannot delete property "a" 206 | ``` 207 | 208 | Changing a properties descriptor 209 | 210 | `Object.defineProperty` can be used to define a new property but also change an existing property’s descriptor if that property is configurable. 211 | 212 | ```js 213 | const obj = {} 214 | 215 | Object.defineProperty(obj, "a", { 216 | value: "A", 217 | enumerable: false, 218 | configurable: true, 219 | writable: false 220 | }) 221 | 222 | Object.defineProperty(obj, "a", { 223 | value: "B", 224 | enumerable: true, 225 | writable: true 226 | }) 227 | 228 | console.log(obj) // { a: "B" } 229 | ``` 230 | 231 | ### get 232 | 233 | The `get` attribute can be an alternative way to provide values for properties. Instead of supplying a static value, you supply a function that will be called to retrieve the value. 234 | 235 | ```js 236 | const obj = {} 237 | 238 | Object.defineProperty(obj, "time", { 239 | get: () => new Date().getTime() 240 | }) 241 | 242 | console.log(obj.time) // 1645720383336 243 | console.log(obj.time) // 1645720383341 244 | ``` 245 | 246 | Note: The function will always be called with empty arguments. 247 | 248 | ### set 249 | 250 | The `set` attribute allows you to have a function be called every time an assignment is attempted for a property. The function will be called with the proposed value as an argument. The function is not required to actually set anything. 251 | 252 | ```js 253 | const obj = {} 254 | 255 | Object.defineProperty(obj, "setMe", { 256 | set: (value) => console.log("You want to set", value, "!") 257 | }) 258 | 259 | obj.setMe = 100 // You want to set 100! 260 | ``` 261 | 262 | ## Data properties VS accessor properties 263 | 264 | There are two ways to categorize properties. Properties are either *data properties* or *accessor properties*. You categorize these different types of properties by their descriptor. Certain descriptor attributes are reserved for data properties, and some are reserved for accessor properties. Additionally, some attributes can exist in both data and accessor properties. This table shows the different attributes and which type they represent[^descriptor-table]. 265 | 266 | | Attribute name | Type of property it represents | 267 | | --- | --- | 268 | | `value` | Data property | 269 | | `writable` | Data property | 270 | | `get` | Accessor property | 271 | | `set` | Accessor property | 272 | | `enumerable` | Data property or accessor property | 273 | | `configurable` | Data property or accessor property | 274 | 275 | One implication of this is that you can’t define a descriptor with e.g., both a `value` and `get` attribute. It’s helpful to keep this in mind when defining descriptors. An error will be thrown if you try to mix attributes for data properties with those for accessor properties. 276 | 277 | ```js 278 | const obj = {} 279 | Object.defineProperty(obj, "a", { 280 | value: "A", 281 | get: function() {} 282 | }) // TypeError: Invalid property descriptor 283 | ``` 284 | 285 | ### Data properties 286 | 287 | Data properties are the most common type of properties. Their value can only change through re-assignment. 288 | 289 | ```js 290 | const obj = {} 291 | 292 | Object.defineProperty(obj,"prop", { 293 | value: 10, 294 | writable: true 295 | }) 296 | 297 | obj.prop // <-- Data property 298 | ``` 299 | 300 | #### Object literal data properties 301 | 302 | Creating object literal properties using a colon (`:`) results in data properties. 303 | 304 | ```js 305 | const obj = { 306 | prop: 10 307 | } 308 | 309 | obj.prop // <-- Data property 310 | ``` 311 | 312 | ### Accessor properties 313 | 314 | Accessor properties are more dynamic. Instead of using the `value` attribute, they use `get` and `set`. These attributes hold methods which are called when reading/assignment of the property is attempted. 315 | 316 | ```js 317 | const obj = {} 318 | 319 | Object.defineProperty(obj,"prop", { 320 | get: () => 10, 321 | set: (val) => {} 322 | }) 323 | 324 | obj.prop // <-- Accessor property 325 | ``` 326 | 327 | #### Getters and setters in object literals 328 | 329 | Getters and setters give a concise syntax for creating accessor properties. You create them by prefixing a method with `get` or `set` in object literals. This will create a property with a descriptor containing your methods on the `get` and `set` attributes. 330 | 331 | ```js 332 | const obj = { 333 | get x() { 334 | console.log("Getting x!") 335 | }, 336 | set x(val) { 337 | console.log("Setting x!") 338 | } 339 | } 340 | 341 | // Same as 342 | 343 | const obj = {} 344 | Object.defineProperty(obj, "x", { 345 | enumerable: true, 346 | configurable: true, 347 | get: function () { 348 | console.log("Getting x!") 349 | }, 350 | set: function () { 351 | console.log("Setting x!") 352 | } 353 | }) 354 | ``` 355 | 356 | # Object restrictions 357 | 358 | Just like you can restrict the functionality of properties, it is also possible to restrict entire objects. There are three methods that apply a varying amount of restriction to your objects. They are: 359 | 360 | - `Object.preventExtensions` - Makes your objects non-*extensible* 361 | - `Object.seal` - Makes your objects *sealed* 362 | - `Object.freeze` - Makes your objects *frozen* 363 | 364 | > ⚠️ It’s not possible to undo any of these restrictions. Once applied, your object will remain restricted for its entire lifetime. It is, however, still possible to copy its properties into a new non-restricted object. 365 | > 366 | 367 | > ⚠️ The following code examples will be in non-strict mode but will contain code that would throw errors in strict mode. A line ending with a `// Failure` comment indicates that an error would be thrown in strict mode. 368 | > 369 | 370 | ### Object.preventExtensions 371 | 372 | By default, objects are extensible. This means it's possible to add new properties to them. `Object.preventExtensions` prevents this by making your objects non-extensible. 373 | 374 | ```js 375 | const obj = { a: "A", b: "B" } 376 | Object.preventExtensions(obj) 377 | 378 | obj.c = "C" // Failure 379 | 380 | console.log(obj) // { a: "A", b: "B" } 381 | ``` 382 | 383 | >⚠️ This will only prevent *adding* new properties and not deleting/changing existing properties. 384 | > 385 | 386 | >💡 You can check if an object is extensible with `Object.isExtensible`. 387 | > 388 | 389 | ### Object.seal 390 | 391 | Sealing your object will make it non-extensible and make every property non-configurable. The result of this is that you can’t create or delete any properties. It is still possible to change the value of properties if they are not read-only. 392 | 393 | ```js 394 | const obj = { a: "A", b: "B" } 395 | Object.seal(obj) 396 | 397 | delete obj.a // Failure 398 | obj.c = "C" // Failure 399 | 400 | console.log(obj) // { a: "A", b: "B" } 401 | ``` 402 | 403 | >💡 You can check if an object is sealed with `Object.isSealed` 404 | > 405 | 406 | ### Object.freeze 407 | 408 | Freezing an object makes it non-extensible and makes every property non-configurable *and* read-only. This effectively prevents the object from being changed in any way. 409 | 410 | ```js 411 | const obj = { a: "A", b: "B" } 412 | Object.freeze(obj) 413 | 414 | delete obj.a // Failure 415 | obj.b = "B2" // Failure 416 | obj.c = "C" // Failure 417 | 418 | console.log(obj) // { a: "A", b: "B" } 419 | ``` 420 | 421 | > 💡 You can check if an object is frozen with `Object.isFrozen`. 422 | > 423 | 424 | 425 | ### Table of object restrictions 426 | 427 | Below you will find a table with the three methods covered above. It allows you to reference which actions can be performed on properties belonging to restricted objects. 428 | 429 | | | Create | Delete | Change | Read | 430 | | --- | --- | --- | --- | --- | 431 | | `Object.preventExtensions` |   ❌ | ✓ | ✓ | ✓ | 432 | | `Object.seal` |   ❌ |   ❌ | ✓ | ✓ | 433 | | `Object.freeze` |   ❌ |   ❌ |   ❌ | ✓ | 434 | 435 | As you can see, the three methods form a sort of hierarchy, with `Object.preventExtensions` being the least restrictive and `Object.freeze` being the most restrictive. 436 | 437 |
438 | 439 | Next chapter: [Chapter 4 - Internal Object Behavior ➡️](./chapter-4.md) 440 | 441 | 442 | [^descriptor-type]: It's a Record Specification Type https://tc39.es/ecma262/#sec-property-descriptor-specification-type 443 | [^descriptor-table]: https://tc39.es/ecma262/#table-object-property-attribute^ -------------------------------------------------------------------------------- /chapters/chapter-4.md: -------------------------------------------------------------------------------- 1 | # Chapter 4 - Internal Object Behavior 2 | 3 | 4 | Peeking into the internals of objects can be a good way to understand how they work. One useful concept to understand is *internal methods*. This chapter covers the different internal methods of different objects and how they behave. It also covers how knowledge about these internal methods can be used in your JavaScript programs. 5 | 6 | ## Internal methods basics 7 | 8 | Every JavaScript object has internal methods, although some have more than others. These methods get called when you have pretty much any interaction with objects. The names of internal methods are labelled by double enclosing brackets ([[ ]]) e.g. [[HasProperty]]. This is to distinguish them from regular object methods. 9 | 10 | You can’t actually call an internal method in your JavaScript program. This is because they exist outside the execution context of your code. They are called by your JavaScript *engine*, not your code. Still, it’s helpful to understand *when* they are called by the engine. 11 | 12 | ## Essential internal methods 13 | 14 | Some internal methods exist on every single object. These are called *essential internal methods*. 15 | 16 | Below is a table with all essential internal methods[^essential-table]. The arguments and return types are written with a notation that resembles an arrow function. An arrow (`=>`) separates the arguments from the return type. The pipe symbol (`|`) is used when a value can be multiple possible types. Square brackets (`[]`) after a type means an array of that type. 17 | 18 | | Internal Method | Arguments- and return type | Description | 19 | | --- | --- | --- | 20 | | [[GetPrototypeOf]] | () => Object \| Null | Get the prototype of the object. | 21 | | [[SetPrototypeOf]] | (Object \| Null) => Boolean | Change the prototype of the object. | 22 | | [[IsExtensible]] | () => Boolean | Check if the object is extensible. | 23 | | [[PreventExtensions]] | () => Boolean | Make the object non-extensible. | 24 | | [[GetOwnProperty]] | (Key) => Descriptor \| Undefined | Get the descriptor of a property. | 25 | | [[DefineOwnProperty]] | (Key, Descriptor) => Boolean | Define a property with a descriptor. | 26 | | [[HasProperty]] | (Key) => Boolean | Check if a property exists. | 27 | | [[Get]] | (Key, Receiver) => Any | Get the value of a property. | 28 | | [[Set]] | (Key, Any, Receiver) => Boolean | Change the value of a property. | 29 | | [[Delete]] | (Key) => Boolean | Delete/remove a property. | 30 | | [[OwnPropertyKeys]] | () => Key[] | Get all property keys. | 31 | 32 | As you can see, some types are different than the ones we defined in chapter one. This is because they are internal and separate from the language types you use in your code. All of the internal types can be converted into language types and vice versa. The new internal types introduced above are: 33 | 34 | - `Key`. A property key. Is converted to/from a string or symbol. 35 | - `Any`. Can be any language type. All instances of this type above represent property values. 36 | - `Descriptor`. The descriptor of a property. Is converted to/from an Object. 37 | - `Receiver`. This argument is always optional. If specified, it will become the `this` of the function. If not, `this` will be the object the method is called on. It will be covered more later. 38 | 39 | ## Internal methods of functions 40 | 41 | As you know already, functions are objects. But functions also have extra internal functionality. Specifically, they have an extra internal method called [[Call]]. 42 | The [[Call]] method is executed internally when the function is called in your code. 43 | 44 | | Internal method | Arguments- and return type | 45 | | --- | --- | 46 | | [[Call]] | (Any, Any[]) => Any | 47 | 48 | ## Internal methods of constructors 49 | 50 | Constructors are functions which have a [[Construct]] internal method in addition to their [[Call]] method. They will be covered much more in [Chapter 7](./chapter-7.md). 51 | 52 | | Internal method | Arguments- and return type | 53 | | --- | --- | 54 | | [[Construct]] | (Any[], Object) => Object | 55 | 56 | ## Reflect: The internal method wrapper 57 | 58 | As mentioned before, you can’t call internal methods because, well, they’re internal. But you can sort of call them with the help of `Reflect`. 59 | 60 | `Reflect` provides minimal wrappers over the internal methods. Meaning, we can call the internal methods *through* `Reflect`. This effectively bridges the gap between the internal methods and our code. 61 | 62 | You will notice that `Reflect` has a lot of similar methods as `Object`. `Object` is more utility-focused and doesn’t cover all the internal methods. It also, unlike `Reflect`, has methods that don’t correspond directly to internal methods. 63 | 64 | Below, you will see a table with all the internal methods and their corresponding `Reflect` method. The `Object` method is also listed if it exists. 65 | 66 | | Internal method | Reflect method | Object method | 67 | | --- | --- | --- | 68 | | [[GetPrototypeOf]] | `Reflect.getPrototypeOf` | `Object.getPrototypeOf` | 69 | | [[SetPrototypeOf]] | `Reflect.setPrototypeOf` | `Object.setPrototypeOf` | 70 | | [[IsExtensible]] | `Reflect.isExtensible` | `Object.isExtensible` | 71 | | [[PreventExtensions]] | `Reflect.preventExtensions` | `Object.preventExtensions` | 72 | | [[GetOwnProperty]] | `Reflect.getOwnPropertyDescriptor` | `Object.getOwnPropertyDescriptor` | 73 | | [[DefineOwnProperty]] | `Reflect.defineProperty` | `Object.defineProperty` | 74 | | [[HasProperty]] | `Reflect.has` | | 75 | | [[Get]] | `Reflect.get` | | 76 | | [[Set]] | `Reflect.set` | | 77 | | [[Delete]] | `Reflect.deleteProperty` | | 78 | | [[OwnPropertyKeys]] | `Reflect.ownKeys` | | 79 | | [[Call]] | `Reflect.apply` | | 80 | | [[Construct]] | `Reflect.construct` | | 81 | 82 | All of the `Reflect` methods take in an object as their first argument. This is the object that the internal method should be called on. The other arguments will match the ones the internal method has. An example is the internal method [[HasProperty]] which takes in a single `Key` argument. The corresponding `Reflect.has` method takes in *two* arguments. The first one being the object that the [[HasProperty]] should be called on. The second argument being the actual argument to [[HasProperty]], the `Key`. 83 | 84 | ```js 85 | const obj = { a: "A" } 86 | console.log(Reflect.has(obj, "a")) // true 87 | ``` 88 | 89 | ## Ordinary objects vs exotic objects 90 | 91 | In JavaScript, not all objects are created equal. Most objects are what’s called *Ordinary Objects*. These include the ones you create yourself. Other Objects are what’s called *exotic objects*. Exotic objects have different internal behavior than ordinary Objects. Specifically, they have a different implementation of some of their internal methods. This allows them to have special internal functionality that isn’t available to ordinary objects. 92 | 93 | Here are all the exotic objects[^exotic-objects]: 94 | 95 | - Bound function objects 96 | - Array objects 97 | - String (primitive wrapper) objects 98 | - Argument objects 99 | - Integer-indexed objects 100 | - Module namespace objects 101 | - Immutable prototype objects 102 | - Proxy objects 103 | 104 | Two of these will be explained further below: array and proxy objects. 105 | 106 | ### Arrays (exotic objects) 107 | 108 | Arrays are exotic objects that have a different [[DefineOwnProperty]] method than ordinary objects[^array]. The [[DefineOwnProperty]] method is used internally to define properties. For ordinary objects, this method is quite straightforward, a property is defined with a key, value, and descriptor. The [[DefineOwnProperty]] of arrays works the same but has special behavior for properties in two special cases: 109 | 110 | - Case 1: The key is called `“length”` 111 | - Case 2: The key is a valid array index. To be this, the key needs to fulfill two criteria: 112 | 1. It should be a *numeric* string. Meaning, a string that is a valid number e.g. `“5”` 113 | 2. The numeric value is a positive number, e.g. `“10”` not `“-10”`. 114 | 115 | >💡 The property values of an array object are also referred to as array *elements.* 116 | > 117 | 118 | >💡 It might sound strange to learn that valid array indexes has to be strings. This is because we normally use numbers to access/define array elements. It might be helpful to think of two different kinds of array indexes: 119 | > - Internal array indexes which has to be strings. 120 | > - External array indexes which can be both strings and numbers. 121 | > The reason external array indexes can be both strings and numbers is that numbers are converted to strings if used as property keys. This was covered in [Chapter 2](./chapter-2.md#property-key-types). 122 | > 123 | 124 | #### Special case 1: The key is called “length” 125 | 126 | It is possible to redefine the `length` property on your arrays. In this case, the internal method will “cut off” extra elements that would make the array exceed the new length. 127 | 128 | ```js 129 | const arr = ["a", "b", "c"] 130 | arr.length = 2 131 | 132 | console.log(arr) // ["a", "b"] 133 | ``` 134 | 135 | In the above example, the last element `“c”` was removed from the array. 136 | 137 | If you set `length` to be greater than the current length, then no elements will be removed. This does create a bit of a strange case, however. In that case, the number of array elements won’t match the length of the array. Turns out this is perfectly valid. 138 | 139 | ```js 140 | const arr = ["a", "b", "c"] 141 | arr.length = 5 142 | 143 | console.log(arr) // [ "a", "b", "c", <2 empty items> ] 144 | ``` 145 | 146 | Your browser might explain the difference between the number of elements and the length with ``. Don’t be fooled by that. These “empty items” don’t actually exist. 147 | 148 | #### Special case 2: The key is a valid array index 149 | 150 | If you define a property with a valid array index key, JavaScript will convert the key to a number and compare it to the value of its `length` property. 151 | If the numeric value of the key is greater than or equal to (`>=`) the value of `length`, it will re-define `length`. The value of `length` will be set to the numeric value of the key `+ 1`. 152 | 153 | ```js 154 | const arr = ["a", "b"] 155 | console.log(arr.length) // 2 156 | 157 | arr["2"] = "c" 158 | console.log(arr.length) // 3 159 | ``` 160 | 161 | In the above example, we start by defining an array with a length of 2. We then define a property using the key `“2”`. The length changes to the numeric value of `“2”` plus one (`2 + 1 = 3`). 162 | 163 | The length will also be re-defined if the numeric value is greater than the length. We can see this below, where we define a property using the key `“5”` on an array with a length of 2. The length changes to `5 + 1 = 6`. 164 | 165 | ```js 166 | const arr = ["a", "b"] 167 | console.log(arr.length) // 2 168 | 169 | arr["5"] = "c" 170 | console.log(arr.length) // 6 171 | ``` 172 | 173 | The length is now 6, but the amount of elements is still 3. Because of this, your browser will likely show some “empty items” inside the array. Remember, these aren’t real elements. 174 | 175 | ```js 176 | console.log(arr) // [ "a", "b", <3 empty items>, "c" ] 177 | ``` 178 | 179 | #### Normal property assignment 180 | 181 | If a property is assigned to the array object that doesn’t fit the two special cases, it will be assigned normally. This won’t affect the length of the array. 182 | 183 | ```js 184 | const arr = ["a", "b", "c"] 185 | arr["Hello"] = "World" 186 | 187 | console.log(arr) // [ "a", "b", "c", Hello: "World" ] 188 | console.log(arr.length) // 3 189 | ``` 190 | 191 | ### Proxies (exotic objects) 192 | 193 | Proxies are the only exotic objects that have a different implementation of every single essential internal method[^proxy]. When one of its internal methods is called, the proxy can call a function of *your* choosing. You can essentially set up listeners that run custom code when internal methods are called. This is really powerful and allows you to create truly flexible objects. 194 | 195 | #### Proxy handlers 196 | 197 | When you initialize a proxy, you give it a *proxy handler*, which is an object. You attach methods to this object which will be called by the proxy. A method on a proxy handler is called a *trap*. A trap is specific to a certain internal method. 198 | 199 | Below you will see a proxy handler that implements every possible trap[^proxy-handler]. This is not required. You only need to define the traps you need. The traps have a comment indicating which type should be returned from them. 200 | 201 | ```js 202 | const proxyHandler = { 203 | getPrototypeOf: (target) => {}, // Object | Null 204 | setPrototypeOf: (target, proto) => {}, // Boolean 205 | isExtensible: (target) => {}, // Boolean 206 | preventExtensions: (target) => {}, // Boolean 207 | getOwnPropertyDescriptor: (target, key) => {}, // Object 208 | defineProperty: (target, key, desc) => {}, // Boolean 209 | has: (target, key) => {}, // Boolean 210 | get: (target, key, receiver) => {}, // Any 211 | set: (target, key, value, receiver) => {}, // Boolean 212 | deleteProperty: (target, key) => {}, // Boolean 213 | ownKeys: (target) => {}, // (String | Symbol)[] 214 | apply: (target, thisArg, args) => {}, // Any 215 | construct: (target, args, newTarget) => {} // Object 216 | } 217 | ``` 218 | 219 | #### Trap name 220 | 221 | The name of the trap is the same as the internal method’s corresponding `Reflect` method. An example could be the internal method [[HasProperty]] which has a corresponding `Reflect` method called `has`. This means that the trap for [[HasProperty]] is also called `has`. 222 | 223 | #### Trap arguments 224 | 225 | The arguments passed to a trap will also match the arguments passed to the corresponding `Reflect` method. The `Reflect.has` method takes in two arguments, `target` and `key`. This means that the trap called `has` will also receive the arguments `target` and `key`. 226 | 227 | #### Trap return type 228 | 229 | The expected return type of a trap will also match that of the corresponding `Reflect` method. The `Reflect.has` method returns a boolean which means that the trap called `has` should also return a boolean. 230 | 231 | #### Proxy targets 232 | 233 | Initializing a proxy also requires another object called the *target*. This will serve as a fallback. If an internal method isn’t implemented in your proxy handler, it will call the internal method on the target instead. The target is also passed to every trap as the first argument. 234 | 235 | The below diagram shows a proxy that has traps for 3 internal methods. As you can see, the internal methods without traps default to the target. 236 | 237 | ![Proxy traps](../images/proxy-traps.png) 238 | 239 | ### Creating Proxies 240 | 241 | You create a new proxy object with the globally available `Proxy` constructor. The first argument is the target and the second is the proxy handler. 242 | 243 | ```js 244 | const proxyObj = new Proxy(target, proxyHandler) 245 | ``` 246 | 247 | The result is the exotic proxy object. A few useful internal methods to trap are covered below. 248 | 249 | #### Trapping [[Get]] 250 | 251 | This will call your trap for every attempt to access property values on your object. The value that you return from your trap will act as the property value. The below proxy object will return a custom string for every property value requested. 252 | 253 | ```js 254 | const proxyObj = new Proxy({}, { 255 | get: (target, key, receiver) => { 256 | return `Value for key ${key}` 257 | } 258 | } 259 | ) 260 | 261 | console.log(proxyObj.key123) // Value for key key123 262 | console.log(proxyObj.helloWorld) // Value for key helloWorld 263 | ``` 264 | 265 | > ⚠️ It still is possible to circumvent this trap by getting the whole descriptor of a property. The descriptor will contain the value or a getter function. Of course, you could trap [[GetOwnProperty]] to prevent this. 266 | > 267 | 268 | #### Trapping [[Set]] 269 | 270 | This will call your trap every time an attempt is made to assign a value to a property. 271 | 272 | ```js 273 | const proxyObj = new Proxy({}, { 274 | set: (target, key, value, receiver) => { 275 | console.log("You want to set", value, "on", key) 276 | return true 277 | } 278 | } 279 | ) 280 | 281 | proxyObj.abc = 1 // You want to set 1 on abc 282 | proxyObj.dfg = 2 // You want to set 2 on dfg 283 | ``` 284 | 285 | It’s important to return `true` from this trap. Otherwise, the assignment will be considered failed and a `TypeError` will be thrown in strict mode. 286 | 287 | > ⚠️ Your [[Set]] trap won’t trigger if a property is defined with a descriptor. Although, it is possible to trap [[DefineOwnProperty]], which will trigger when properties are defined with descriptors. 288 | > 289 | 290 | #### receiver in [[Get]] and [[Set]] traps 291 | 292 | You may have noticed how the [[Get]] and [[Set]] traps take in an argument called `receiver`. It can be important in certain cases. You can read about it in [Appendix C](./appendix-c.md). I recommend that you finish [chapter 5](./chapter-5.md) first. 293 | 294 | #### Trapping [[OwnPropertyKeys]] 295 | 296 | This allows you to return a custom set of property keys for the object. 297 | 298 | ```js 299 | const proxyObj = new Proxy({}, { 300 | ownKeys: (target) => ["a", "b", "c"] 301 | } 302 | ) 303 | 304 | console.log(Reflect.ownKeys(proxyObj)) // ["a", "b", "c"] 305 | console.log(Object.keys(proxyObj)) // [] 306 | ``` 307 | 308 | As you can see, `Reflect.ownKeys` returns the keys from our trap, but `Object.keys` does not. The reason for this is that `Object.keys` only returns keys of enumerable properties. The keys we return from our proxy trap don’t actually exist, meaning they don’t have a descriptor with an `enumerable` attribute. 309 | 310 | #### Trapping [[GetOwnPropertyDescriptor]] 311 | 312 | This will allow you to return custom descriptors for any call to [[GetOwnPropertyDescriptor]]. 313 | 314 | Using this, we can fix the above example by always returning a descriptor specifying that the property is enumerable. 315 | 316 | ```jsx 317 | const proxyObj = new Proxy({}, { 318 | ownKeys: (target) => ["a", "b", "c"], 319 | getOwnPropertyDescriptor: (target, key) => { 320 | return { enumerable: true, configurable: true } 321 | } 322 | } 323 | ) 324 | 325 | console.log(Object.keys(proxyObj)) // ["a", "b", "c"] 326 | ``` 327 | 328 | This will also allow our “fake” property keys to be used with the spread operator (`{...obj}`) and the for..in loop. 329 | 330 | > ⚠️ For the descriptor returned, it is required to have the `configurable` attribute set to `true` unless the target explicitly has `configurable` set to `false` for the same property. Don't worry too much about why. This is just a quirk of proxies. 331 | > 332 | 333 | #### Trap ideas 334 | 335 | The traps shown so far have been pretty simple. Here are some more interesting ideas for traps. 336 | 337 | - Validate property data before assigning it. 338 | - Restrict access to certain properties. 339 | - Limit property values to be of a certain type. 340 | - Automatically update DOM when an object changes. 341 | 342 |
343 | 344 | Next chapter: [Chapter 5 - Prototypes ➡️](./chapter-5.md) 345 | 346 | [^essential-table]: This is a modified version of this table https://tc39.es/ecma262/#table-essential-internal-methods 347 | [^exotic-objects]: https://tc39.es/ecma262/#sec-built-in-exotic-object-internal-methods-and-slots 348 | [^array]: https://tc39.es/ecma262/#sec-array-exotic-objects-defineownproperty-p-desc 349 | [^proxy]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots 350 | [^proxy-handler]: https://tc39.es/ecma262/#table-proxy-handler-methods 351 | -------------------------------------------------------------------------------- /chapters/chapter-5.md: -------------------------------------------------------------------------------- 1 | # Chapter 5 - Prototypes 2 | 3 | Have you ever thought about how you are able to call `.toString` on seemingly every object? 4 | 5 | ```js 6 | const obj = {} 7 | console.log(Reflect.ownKeys(obj)) // [] 8 | 9 | console.log(obj.toString()) // [object Object] 10 | ``` 11 | 12 | How is this possible if the object doesn’t have any properties? The answer is *prototypes.* Prototypes are how you implement inheritance in JavaScript. They allow you have an object inherit properties from another object. Our empty object inherited the `.toString` method from a standard object called `Object.prototype`. 13 | 14 | ```js 15 | const obj = {} 16 | console.log(obj.toString === Object.prototype.toString) // true 17 | ``` 18 | 19 | ## The Prototype chain 20 | 21 | All objects have an internal property called [[Prototype]]. The value of this property is either `null` or a reference to another object. The value of this internal property is referred to as the *prototype* of the object. You can’t directly access it but you can retrieve it using `Object.getPrototypeOf` or `Reflect.getPrototypeOf`. 22 | 23 | ```js 24 | const obj = {} 25 | const objProto = Reflect.getPrototypeOf(obj) 26 | console.log(typeof objProto) // object 27 | ``` 28 | 29 | In the above example, we define an empty object literal. We then use `Reflect.getPrototypeOf`, which gives us the prototype of that object. As we can see, the prototype of our object is also an object. This might seem a bit strange; we never gave `obj` a prototype. Object literals have a default prototype. More on that later. 30 | 31 | We now know that objects can have an internal reference to another object known as their prototype. This becomes more interesting when you realize that since prototypes are also objects, prototypes can have their *own* prototype. This leads to something called the *prototype chain.* It’s the linking between potentially multiple objects through prototypes. 32 | 33 | ![Prototype chain](../images/prototype-chain.png) 34 | 35 | Above, we can see a prototype chain starting with Object A. Object A has the prototype Object B. Object B has the prototype Object C. A prototype chain ends if the prototype of an object is `null`. This is the case for Object C. 36 | 37 | >💡 It’s important to take note of the direction of the arrows. The prototype of Object A is Object B, but not vice versa. Object B knows nothing about Object A. Object A is not part of the prototype chain of Object B. 38 | > 39 | 40 | ### Inheritance 41 | 42 | The prototype chain isn’t just used to link objects together; it’s used for inheritance. Specifically, if an object lacks a property, it can inherit it from its prototype chain. To understand how this works, it’s important to understand what happens when you try to access a property. It goes as follows[^get]: 43 | 44 | 1. If the property exists on the object, **return** it. 45 | 2. Else, retrieve the object’s prototype. If that’s `null` **return** `undefined`. 46 | 3. Else, go back to step 1. This time let the “object” be the prototype. 47 | 48 | This means your JavaScript engine will “climb up” the prototype chain in search of the property. If it doesn’t exist on the object, it will check the prototype, then that prototype’s prototype, etc., until it finds it or reaches `null`. 49 | 50 | Consider these three objects. They each have a unique property. How many of these properties are accessible to Object A? 51 | 52 | ![Prototype chain unique properties](../images/prototype-unique.png) 53 | 54 | The answer is all of them. Property X exists directly on Object A, and Property Y and Z exist in its prototype chain. 55 | 56 | What if the same property key exists in multiple places in a prototype chain? Which property value will be returned when Property X is accessed on Object A below? 57 | 58 | ![Prototype chain same properties](../images/prototype-same.png) 59 | 60 | The answer is Property X of Object B. It comes first in the prototype chain. When the JavaScript engine goes through the prototype chain to find a property, it stops when it finds the first occurrence. 61 | 62 | >💡 Properties that exist directly on an object are referred to as its *own* properties. This term is used in methods like `Reflect.getOwnPropertyDescriptor` and `Reflect.ownKeys`. These functions won’t climb up the prototype chain. 63 | > 64 | 65 | ## Standard prototypes 66 | 67 | Some objects are used as the default prototype for the objects you create. It’s useful to understand these, so you know which properties your objects inherit. Two important standard prototypes will be covered below. 68 | 69 | #### Object.prototype 70 | 71 | If you create an object literal, the prototype will be an object called `Object.prototype`. It isn’t just specific to object literals, however. It is no doubt the most important object in the context of prototypes. It is present in the prototype chain of practically every object in JavaScript. It’s usually found at the end of the chain right before `null`. 72 | 73 | For object literals, it’s the first and only prototype. 74 | 75 | Below, the properties of `Object.prototype` are logged. 76 | 77 | ```js 78 | const obj = {} 79 | const proto = Reflect.getPrototypeOf(obj) 80 | console.log(proto === Object.prototype) // true 81 | console.log(Reflect.ownKeys(proto)) 82 | /* [ 83 | 'constructor', 84 | '__defineGetter__', 85 | '__defineSetter__', 86 | 'hasOwnProperty', 87 | '__lookupGetter__', 88 | '__lookupSetter__', 89 | 'isPrototypeOf', 90 | 'propertyIsEnumerable', 91 | 'toString', 92 | 'valueOf', 93 | '__proto__', 94 | 'toLocaleString' 95 | ] */ 96 | ``` 97 | 98 | #### Function.prototype 99 | 100 | The prototype of the functions you create will be `Function.prototype`. It contains useful methods like `apply`, `bind`, and `call`, which will be covered later in [Chapter 6](./chapter-6.md#traditional-function-using-bind-call-or-apply). 101 | 102 | ```js 103 | function func() {} 104 | const proto = Reflect.getPrototypeOf(func) 105 | console.log(proto === Function.prototype) // true 106 | console.log(Reflect.ownKeys(proto)) 107 | /* [ 108 | 'length', 109 | 'name', 110 | 'arguments', 111 | 'caller', 112 | 'constructor', 113 | 'apply', 114 | 'bind', 115 | 'call', 116 | 'toString', 117 | Symbol(Symbol.hasInstance) 118 | ] */ 119 | ``` 120 | 121 | The prototype of `Function.prototype` is `Object.prototype`. Notice how both Objects contain a `.toString` method. This means that functions don’t access the `.toString` from `Object.prototype`, but instead access a more function specific `.toString` method from `Function.prototype`. 122 | 123 | ![Prototype chain of func](../images/prototype-function.png) 124 | 125 | ## The .\_\_proto\_\_ property 126 | 127 | If you open your browser console and access a property called `__proto__` on an object, you will receive its prototype. 128 | 129 | ```js 130 | const obj = {} 131 | const objProto = Reflect.getPrototypeOf(obj) 132 | console.log(objProto === obj.__proto__) // true 133 | ``` 134 | 135 | Because of this, many people assume that the prototype of an object is stored *on* the `.__proto__` property. This is wrong. The prototype of an object is stored internally, and `.__proto__` is just an accessor property that exposes it. 136 | 137 | It is not recommended that you use `.__proto__` to access or set prototypes. It’s only guaranteed to be supported in browsers, and even then is only kept for compatibility purposes[^proto]. Deno is an example of a modern JavaScript runtime that doesn’t support `.__proto__`. 138 | 139 | ## Setting prototypes on objects 140 | 141 | There are two ways to set a prototype on an object. The first is with `Object.create`. This will create a new empty object with a prototype you supply as an argument. 142 | 143 | ```js 144 | const proto = { canYouAccessMe: true } 145 | const obj = Object.create(proto) 146 | console.log(obj.canYouAccessMe) // true 147 | ``` 148 | 149 | The second way is with `Object.setPrototypeOf` or `Reflect.setPrototypeOf`. These functions replace the prototype on an already created object. 150 | 151 | ```js 152 | const proto = { canYouAccessMe: true } 153 | const obj = {} 154 | Reflect.setPrototypeOf(obj, proto) 155 | console.log(obj.canYouAccessMe) // true 156 | ``` 157 | 158 | > ⚠️ Changing the prototype of an existing object can be a performance issue and should generally be avoided. 159 | > 160 | 161 |
162 | 163 | Next chapter: [Chapter 6 - this ➡️](./chapter-6.md) 164 | 165 | [^get]: https://tc39.es/ecma262/#sec-ordinaryget 166 | [^proto]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/proto -------------------------------------------------------------------------------- /chapters/chapter-6.md: -------------------------------------------------------------------------------- 1 | # Section 6 - this 2 | 3 | The `this` keyword can provide useful context for your code. 4 | 5 | ```js 6 | const obj = { 7 | canYouSeeMe: true, 8 | func: function () { 9 | console.log(this.canYouSeeMe) // true 10 | } 11 | } 12 | obj.func() 13 | ``` 14 | 15 | Above, we define an object with two properties. The first property is called `canYouSeeMe` and the second is a method called `func`. Interestingly, `func` is able to access `canYouSeeMe` using `this`. 16 | 17 | `this` can be used anywhere in your code, and depending on where it’s used, its value will vary quite a lot. This chapter will focus on the `this` inside functions in six different cases. A distinction will be made between *traditional functions* (`function () {}`) and *arrow functions* (`() => {}`). Below you will see the different cases that will be covered. 18 | 19 | | How function is being called | Traditional function `this` | Arrow function `this` | 20 | | --- | --- | --- | 21 | | On its own | ? | ? | 22 | | On an object | ? | ? | 23 | | Using `bind`, `call`, or `apply` | ? | ? | 24 | 25 | >💡 Some of these cases have different outcomes depending on if they’re in strict mode or not. Make sure you understand strict mode before you proceed. You can read about strict mode in [Appendix B](./appendix-b.md). 26 | > 27 | 28 | # globalThis 29 | 30 | Before diving in, it’s important to understand the `globalThis` keyword. In certain cases, it will be used as a fallback for `this`. It, unlike `this`, has the same value no matter the context. Below you can see the value of `globalThis` in different contexts. 31 | 32 | | Context | `globalThis` | 33 | | --- | --- | 34 | | NodeJS | `global` | 35 | | Deno | `window` | 36 | | All major browsers | `window` | 37 | 38 | `globalThis` is not to be confused with `this` in the global scope (outside any function). In browsers, they’re the same, but not in NodeJS or Deno. 39 | 40 | | Context | `this` in global scope | 41 | | --- | --- | 42 | | NodeJS | Empty object | 43 | | Deno | `undefined` | 44 | | All major browsers | `window` | 45 | 46 | # Traditional function on its own 47 | 48 | If you run a traditional function on its own, the value of its `this` will depend if the function is in strict mode or not. 49 | 50 | If in strict mode, `this` will be `undefined`. If not in strict mode, `this` will be `globalThis`. 51 | 52 | ```js 53 | "use strict" 54 | 55 | function func() { 56 | return this 57 | } 58 | 59 | console.log(func() === undefined) // true 60 | ``` 61 | 62 | ```js 63 | function func() { 64 | return this 65 | } 66 | 67 | console.log(func() === globalThis) // true 68 | ``` 69 | 70 | # Traditional function on an object 71 | 72 | Calling a traditional function on an object will result in its `this` being the object it's called on. 73 | 74 | ```js 75 | const obj = { 76 | func: function () { 77 | return this 78 | } 79 | } 80 | 81 | console.log(obj.func() === obj) // true 82 | ``` 83 | 84 | This provides a useful way to retain and share state between function calls. 85 | 86 | ```js 87 | const obj = { 88 | counter: 0, 89 | increment: function () { 90 | this.counter += 1 91 | } 92 | } 93 | 94 | console.log(obj.counter) // 0 95 | obj.increment() 96 | obj.increment() 97 | console.log(obj.counter) // 2 98 | ``` 99 | 100 | > ⚠️ `this` doesn’t follow a function if you remove it from its object, e.i. if you assign it to a variable and call it on its own. 101 | >```js 102 | >const obj = { 103 | > func: function () { 104 | > return this 105 | > } 106 | >} 107 | > 108 | >const func = obj.func 109 | >console.log(func()) // globalThis || undefined 110 | >``` 111 | > 112 | 113 | # Traditional function using bind, call, or apply 114 | 115 | There are three methods that let you manipulate the `this` inside functions. They are called `bind`, `call`, and `apply`. They exist on `Function.prototype`, meaning they exist on all functions. 116 | 117 | ### bind 118 | 119 | `bind` creates a new function with a `this` that you specify. The function it creates will have your `this` no matter the context. 120 | 121 | ```js 122 | function f1() { 123 | return this 124 | } 125 | 126 | const f2 = f1.bind({ iAmThis: true }) 127 | console.log(f2()) // { iAmThis: true } 128 | ``` 129 | 130 | ### call 131 | 132 | `call` is similar to `bind`, but it calls the function directly with the new `this` instead of creating a new function. The first argument is the `this`, and the rest are the arguments that should be passed to the function. 133 | 134 | ```jsx 135 | function increaseAge(amount) { 136 | this.age += amount 137 | } 138 | 139 | const person = { age: 30 } 140 | 141 | increaseAge.call(person, 5) 142 | 143 | console.log(person) // { age: 35 } 144 | ``` 145 | 146 | ### apply 147 | 148 | `apply` is the same as `call` except you pass arguments as an array. 149 | 150 | ```jsx 151 | function setCoords(x, y) { 152 | this.x = x 153 | this.y = y 154 | } 155 | 156 | const person = {} 157 | 158 | setCoords.apply(person, [5, 10]) 159 | 160 | console.log(person) // { x: 5, y: 10 } 161 | ``` 162 | 163 | # Arrow function on its own or on an object 164 | 165 | >💡 An arrow function called on its own or on an object will have the same `this`. 166 | > 167 | 168 | Arrow functions don’t have their own `this` binding. Instead, the `this` inside arrow functions is based on the scope they are defined within. 169 | 170 | If an arrow function is defined inside a traditional function, it will have the same `this` as the traditional function. If it’s not defined inside a traditional function, its `this` will be the `this` of the global scope. 171 | 172 | It’s important to remember that the `this` is based on where the arrow function is *defined*, not where it’s called. This turns out to be a useful feature when it comes to callbacks. Callbacks are functions that you supply to other functions as arguments. Doing this with traditional functions means you often lose control of `this`. That’s because you usually don’t control *how* the callback is called. Luckily, arrow functions don’t care how they’re called, only where they’re defined. 173 | 174 | An interesting application of arrow functions can be seen with `setTimeout`. `setTimeout` is a function that you call with two arguments. The first is a callback, and the second is a number representing milliseconds. `setTimeout` will call your callback after the time has passed that you specified in milliseconds. 175 | 176 | Below we have two objects. Both of them have a method called `slowIncrement`, which calls `setTimeout` with a callback to be called in 500 milliseconds. Both callbacks access `this` to increment a counter. The first callback is a traditional function and the second is an arrow function. 177 | 178 | ```js 179 | const obj1 = { 180 | count: 0, 181 | slowIncrement: function () { 182 | setTimeout(function () { 183 | this.count += 1 184 | console.log(this.count) 185 | }, 500) 186 | } 187 | } 188 | obj1.slowIncrement() // NaN, this.count is undefined 189 | 190 | const obj2 = { 191 | count: 0, 192 | slowIncrement: function () { 193 | setTimeout(() => { 194 | this.count += 1 195 | console.log(this.count) 196 | }, 500) 197 | } 198 | } 199 | 200 | obj2.slowIncrement() // 1 201 | ``` 202 | 203 | As we can see, the traditional function callback isn’t able to access the `this` we want. It instead accesses a `this` we don’t control from inside the `setTimeout` function. The arrow function callback is able to access the same `this` as `slowIncrement`, which is `obj2`. Meaning that the arrow function was able to “take on” the `this` of the traditional function it was *defined* within. 204 | 205 | >💡 You can, of course, always create a traditional function with a controllable `this` using `bind`. 206 | > 207 | 208 | # Arrow function using bind, call, or apply 209 | 210 | Because arrow functions don’t have their own `this` binding, `bind`, `call`, or `apply` will have no effect on them. 211 | 212 | ```js 213 | const func = () => this 214 | 215 | const customThis = {} 216 | 217 | console.log(func.bind(customThis)() === customThis) // false 218 | console.log(func.call(customThis) === customThis) // false 219 | console.log(func.apply(customThis) === customThis) // false 220 | ``` 221 | 222 |
223 | 224 | Next chapter: [Chapter 7 - Constructors ➡️](./chapter-7.md) -------------------------------------------------------------------------------- /chapters/chapter-7.md: -------------------------------------------------------------------------------- 1 | # Chapter 7 - Constructors 2 | 3 | 4 | Constructors provide a convenient way to create instances of objects. Below we use the `Date` constructor to create a new date object. 5 | 6 | ```js 7 | const obj = new Date() 8 | console.log(typeof obj) // object 9 | ``` 10 | 11 | # What is a constructor? 12 | 13 | All functions in JavaScript have an internal method called [[Call]]. This method is used to execute the code inside the function. Some JavaScript functions have another internal method called [[Construct]]. It, too, executes the code in the function, although slightly differently. Functions that have a [[Construct]] method are called constructors[^function-table]. 14 | 15 | ### new 16 | 17 | But how do you call a function using [[Construct]] instead of [[Call]]? You use the `new` operator. We can see this with the `Date` constructor. Below, we call it in two ways. The first is with the `new` operator, and the second is without. Both ways work (but produces different results). 18 | 19 | ```js 20 | const obj1 = new Date() // Internally calling [[Construct]] 21 | const obj2 = Date() // Internally calling [[Call]] 22 | ``` 23 | 24 | ### Reflect.construct 25 | 26 | Another way to call a constructor using [[Construct]] is with `Reflect.construct`. The first argument is the constructor, and the second is an array of arguments. 27 | 28 | ```js 29 | const obj = Reflect.construct(Date, []) 30 | console.log(typeof obj) // object 31 | ``` 32 | 33 | # Which functions have a [[Construct]] method? 34 | 35 | Some functions have an internal [[Construct] method, and some don’t. You will now see some examples of constructors and non-constructors. 36 | 37 | >⚠️ If you attempt to call [[Construct]] on a non-constructor function, it will throw a `TypeError`. 38 | > 39 | 40 | ### Classes 41 | 42 | Classes are a common way to create instances of objects. They are constructors. Much more on them in [chapter 8](./chapter-8.md). 43 | 44 | ```js 45 | class Func {} 46 | new Func() // OK 47 | ``` 48 | 49 | ### Traditional functions 50 | 51 | Generally speaking, traditional functions are valid constructors. This might be surprising if you’re mainly using classes. 52 | 53 | ```js 54 | function Func() {} 55 | new Func() // OK 56 | ``` 57 | 58 | An exception to this is async functions. They are not constructors. 59 | 60 | ```js 61 | async function Func() {} 62 | new Func() // TypeError: Not a constructor 63 | ``` 64 | 65 | ### Arrow functions 66 | 67 | Arrow functions are not constructors. 68 | 69 | ```js 70 | const Func = () => {} 71 | new Func() // TypeError: Not a constructor 72 | ``` 73 | 74 | # [[Construct]] 75 | 76 | What exactly does [[Construct]] do when called on a function? Here is a brief overview[^construct-steps]: 77 | 78 | 1. Create an empty object 79 | 2. Set its prototype to the value of the constructor’s `.prototype` property 80 | 3. Execute the code with `this` being a reference to the object 81 | 4. Return the object 82 | 83 | Steps 2 and 3 will be covered further below. 84 | 85 | ### The .prototype property (step 2) 86 | 87 | All constructors will have a property called `.prototype`. Below, we define a constructor called `Car` and verify that it has a `.prototype` property. As we can see, the type is an object. 88 | 89 | ```js 90 | function Car() { 91 | console.log(typeof Car.prototype) // object 92 | ``` 93 | 94 | Given the name of the property, it’s easy to think that it references the prototype of the *constructor*. That’s wrong. 95 | 96 | ```js 97 | function Car() {} 98 | const CarProto = Reflect.getPrototypeOf(Car) 99 | console.log(CarProto === Car.prototype) // false 100 | ``` 101 | 102 | The `.prototype` property actually holds the prototype that the constructor wishes to give *instances* created with it. 103 | 104 | ```js 105 | function Car() {} 106 | const instance = new Car() 107 | const instanceProto = Reflect.getPrototypeOf(instance) 108 | console.log(instanceProto === Car.prototype) // true 109 | ``` 110 | 111 | ### The this inside a constructor (step 3) 112 | 113 | Consider this function: 114 | 115 | ```js 116 | function Car(speed) { 117 | this.speed = speed 118 | } 119 | ``` 120 | 121 | It assigns a property to its `this` and then ends. Calling the function using [[Construct]] will execute the code inside it and return its `this` to us. 122 | 123 | ```js 124 | function Car(speed) { 125 | this.speed = speed 126 | } 127 | 128 | console.log(new Car(5)) // { speed: 5 } 129 | ``` 130 | 131 | As we can see, the return value we get from invoking [[Construct]] (using `new`) is an object with a `.speed` property. Meaning that the “instance” we receive from a constructor is actually an instance of its `this`. 132 | 133 | ### Putting this and .prototype together (creating methods) 134 | 135 | Turns out, the `.prototype` property of a constructor is the perfect place to define methods for its instances. Consider the `Car` constructor from before. Let’s give it a method called `drive`. 136 | 137 | ```js 138 | function Car(speed) { 139 | this.speed = speed 140 | } 141 | 142 | Car.prototype = { 143 | drive() { 144 | console.log(`Wr${"o".repeat(this.speed)}m!`) 145 | } 146 | } 147 | 148 | const car1 = new Car(2) 149 | car1.drive() // Wroom! 150 | const car2 = new Car(5) 151 | car2.drive() // Wrooooom! 152 | ``` 153 | 154 | Above, two instances are being created, `car1` and `car2`. As we can see, they are both able to access the `drive` method defined on `Car.prototype`. 155 | 156 | ![Car constructor and its instances](../images/car-constructor.png) 157 | 158 | This is a common pattern when giving methods to JavaScript instances. The two instances don’t have a `drive` method each. Instead, they *share* the `drive` method by both having `Car.prototype` as their prototype. 159 | 160 | >💡 You might be wondering why the result of calling `drive` on the instances is different. After all, they access the exact same method. The difference comes from the `this` inside the `drive` method. `drive` is a traditional function, so its `this` is the object it's called *on*. That explains the difference between calling `car1.drive()` and `car2.drive()`. 161 | > 162 | > This was covered in [Chapter 6](./chapter-6.md#traditional-function-on-an-object). 163 | > 164 | 165 | >💡 It should start to make sense why the standard prototypes we covered in [chapter 5](./chapter-5.md) are called `Object.prototype` and `Function.prototype`. Like our `Car` function, `Object` and `Function` are constructors. They are standard constructors that provide alternative ways to create objects and functions. 166 | 167 | # instanceof 168 | 169 | The `instanceof` operator tells you if an object contains a constructor’s `.prototype` property somewhere in its prototype chain. 170 | 171 | To illustrate this, let’s create a date and visualize its prototype chain. 172 | 173 | ```js 174 | const date = new Date() 175 | ``` 176 | 177 | ![date prototype chain](../images/date-instanceof.png) 178 | 179 | As we can see, both `Date.prototype` and `Object.prototype` exist in its prototype chain. This means that `instanceof` will return `true` for both `Date` and `Object`. 180 | 181 | ```js 182 | const date = new Date() 183 | 184 | console.log(date instanceof Date) // true 185 | console.log(date instanceof Object) // true 186 | ``` 187 | 188 | >💡 In reality, `instanceof` will first check for a method on the constructor using the well-known symbol key `Symbol.hasInstance`. If such a method exists, `instanceof` will call it and return its result. If it doesn’t exist, `instanceof` will work as described above. 189 | >This essentially means that the result of `instanceof` can be customized for individual constructors. However, constructors rarely take advantage of this. 190 | > 191 | 192 |
193 | 194 | Next chapter: [Chapter 8 - Classes ➡️](./chapter-8.md) 195 | 196 | [^function-table]: https://tc39.es/ecma262/#table-additional-essential-internal-methods-of-function-objects 197 | [^construct-steps]: https://tc39.es/ecma262/#sec-ecmascript-function-objects-construct-argumentslist-newtarget -------------------------------------------------------------------------------- /chapters/chapter-8.md: -------------------------------------------------------------------------------- 1 | # Chapter 8 - Classes 2 | 3 | Classes give you a way to create instances of objects with common functionality. You create them using the `class` keyword. Here is an example of a class: 4 | 5 | ```js 6 | class Person { 7 | constructor(name) { 8 | this.name = name 9 | } 10 | 11 | sayName() { 12 | console.log("My name is", this.name) 13 | } 14 | 15 | static askName() { 16 | console.log("What is your name?") 17 | } 18 | } 19 | 20 | const paul = new Person("Paul") 21 | Person.askName() // What is your name? 22 | paul.sayName() // My name is Paul 23 | ``` 24 | 25 | Under the hood, classes mainly use concepts covered in the previous chapters. In fact, almost all the functionality classes provide can be implemented without them[^func-class-diff]. This was done for many years before classes were introduced in 2015. Nevertheless, classes make development a lot easier. 26 | 27 | # Classes are constructors 28 | 29 | The first thing you need to know about classes is that they are constructors. To clarify, the class *itself* is a constructor. Meaning a class is a *function* with both a [[Call]] and [[Construct]] internal method. However, if you call a class using [[Call]], it will throw a `TypeError`. This means there is practically only one way to call classes, with [[Construct]]. 30 | 31 | ```js 32 | new MyClass() // Internally calling [[Construct]], OK 33 | MyClass() // Internally calling [[Call]], throws TypeError 34 | ``` 35 | 36 | # The special constructor method 37 | 38 | A class method called `constructor` will be considered special to classes. To understand it, it helps to remember what a class is. It’s a function. A function, of course, has code inside it. Turns out, the code inside a class comes from its `constructor` method. It sounds strange but bear with me. 39 | 40 | Let’s create a class and a traditional function that do the same when called with `new`. 41 | 42 | ```js 43 | class MyClass { 44 | constructor() { 45 | console.log("Hello!") 46 | } 47 | } 48 | 49 | function MyFunc() { 50 | console.log("Hello!") 51 | } 52 | 53 | new MyClass() // Hello! 54 | new MyFunc() // Hello! 55 | ``` 56 | 57 | For the class, isn’t it a bit strange that we executed the `console.log` code by calling `new MyClass()` and didn’t have to call `new MyClass.constructor()`? After all, the `console.log` code exists inside a method inside the class. But this works because the code of the `constructor` method becomes the code of the class function itself. 58 | 59 | ![A class and function side-by-side](../images/class-func.png) 60 | 61 | >⚠️ A class definition doesn’t actually produce a traditional function definition like shown above. Both definitions create their own internal function object. However, it’s safe to assume, for now, that they produce an identical function object. Meaning, you can use the above figure as a helpful mental model. 62 | > 63 | 64 | >💡 It’s not required to define a `constructor` method. If you don’t, it’s the equivalent of defining an empty one (`constructor(){}`). 65 | > 66 | >This doesn’t apply to *derived classes* which will be covered later. 67 | > 68 | 69 | # Own properties 70 | 71 | Certain properties will be own properties of objects created with classes. Meaning, the properties will exist *on* the instance. Not in its prototype chain or anywhere else. 72 | 73 | Classes have two ways to give their instances own properties: 74 | 75 | - Instance properties 76 | - Field declarations 77 | 78 | ### Instance properties 79 | 80 | Instance properties are defined inside the special `constructor` class method we covered above. You define them by assigning properties to `this`. 81 | 82 | ```jsx 83 | class Person { 84 | constructor(name) { 85 | this.name = name 86 | } 87 | } 88 | 89 | const paul = new Person("Paul") 90 | console.log(paul) // { name: "Paul" } 91 | ``` 92 | 93 | Above, we have a class called `Person` that has a `constructor` method that assigns a value to `this.name`. When we invoke [[Construct]] on the class (using `new`), we get an object with our instance property, `name`. 94 | 95 | We can mimic this with a traditional function: 96 | 97 | ```js 98 | function Person(name) { 99 | this.name = name 100 | } 101 | 102 | const paul = new Person("Paul") 103 | console.log(paul) // { name: "Paul" } 104 | ``` 105 | 106 | ### Field declarations 107 | 108 | Field declarations are another way to define own properties on class instances. They are defined in the body of a class (outside any method). 109 | 110 | ```js 111 | class Cat { 112 | livesLeft = 9 113 | } 114 | 115 | const garfield = new Cat() 116 | console.log(garfield) // { livesLeft: 9 } 117 | ``` 118 | 119 | # Methods 120 | 121 | >💡 This covers non-static methods. 122 | > 123 | 124 | Defining a method on a class will actually define it on the class's `.prototype` property. Remember, a class is a constructor, and the `.prototype` property is special to constructors. The prototype of instances created with a constructor will come from the *constructors* `.prototype` property. To illustrate this, let’s create two instances from a class with a `sayName` method. 125 | 126 | ```js 127 | class Person { 128 | constructor(name) { 129 | this.name = name 130 | } 131 | sayName() { 132 | console.log("My name is", this.name) 133 | } 134 | } 135 | 136 | const paul = new Person("Paul") 137 | const owen = new Person("Owen") 138 | 139 | paul.sayName() // My name is Paul 140 | owen.sayName() // My name is Owen 141 | ``` 142 | 143 | At first glance, it seems like `paul` and `owen` each have their own `sayName` method. But in reality, they both share the exact same method. 144 | 145 | ```js 146 | console.log(paul.sayName === owen.sayName) // true 147 | ``` 148 | 149 | This is because they both inherited the method from their prototype. Since `paul` and `owen` were created as instances of a constructor, their prototype comes from the constructor’s `.prototype` property. In this case, the constructor is `Person`. 150 | 151 | ```jsx 152 | const paulProto = Reflect.getPrototypeOf(paul) 153 | const owenProto = Reflect.getPrototypeOf(owen) 154 | console.log(paulProto === Person.prototype) // true 155 | console.log(owenProto === Person.prototype) // true 156 | ``` 157 | 158 | Below is an illustration of how `paul` and `owen` access the `sayName` method. 159 | 160 | ![Paul and Owen accessing sayName through Person.prototype](../images/person-class.png) 161 | 162 | # Static members 163 | 164 | Static members, that is, static field declarations and methods, exist as own properties of the class *itself*. They do not appear in the prototype chain of instances, and they can only be accessed directly on the class. 165 | 166 | ```js 167 | class Person { 168 | static totalPeople = 0 169 | static incrementPeople() { 170 | Person.totalPeople += 1 171 | } 172 | } 173 | 174 | Person.incrementPeople() 175 | console.log(Person.totalPeople) // 1 176 | ``` 177 | 178 | Above, we have a class with two static members, `totalPeople` and `incrementPeople`. We access these members directly on `Person`. 179 | 180 | # Derived classes 181 | 182 | Classes can inherit functionality from other classes using the `extends` keyword. A class that “extends” another class is called a *derived class*. Setting up this inheritance will add another link in the prototype chain of class instances. Specifically, the object stored on the `.prototype` property of a derived class will have a prototype pointing to the `.prototype` property of the parent class. 183 | 184 | To explore this, let’s create a derived class called `Cat` that derives from `Animal`. We can then create an instance called `cat` and examine its prototype chain. 185 | 186 | ```js 187 | class Animal {} 188 | class Cat extends Animal {} 189 | 190 | const cat = new Cat() 191 | ``` 192 | 193 | ![Cat deriving from Animal](../images/derived-class.png) 194 | 195 | As we can see, the prototype of `Cat.prototype` is `Animal.prototype`. This means that properties defined on `Animal.prototype` are available to our instance `cat` through prototype chain inheritance. We can see this by defining a `sayHi` method on `Animal`. 196 | 197 | ```js 198 | class Animal { 199 | sayHi() { 200 | console.log("Hi! I'm an animal") 201 | } 202 | } 203 | 204 | class Cat extends Animal {} 205 | 206 | const cat = new Cat() 207 | 208 | cat.sayHi() // Hi! I'm an animal 209 | ``` 210 | 211 | Above, we can see that `sayHi` is available to our `cat` instance. It’s inherited from `Animal.prototype`. 212 | 213 | ```js 214 | console.log(cat.sayHi === Animal.prototype.sayHi) // true 215 | ``` 216 | 217 | ### Super keyword 218 | 219 | The `super` keyword allows a derived class to interact with its parent. 220 | 221 | #### this of a super call 222 | 223 | Calling `super` inside the `constructor` method of a derived class calls [[Construct]] on the parent class. When this happens, the `this` inside both functions is the same object. This can be seen in the below demonstration. 224 | 225 | ```js 226 | let this1 227 | let this2 228 | 229 | class Parent { 230 | constructor() { 231 | this1 = this 232 | } 233 | } 234 | 235 | class Derived extends Parent { 236 | constructor() { 237 | super() 238 | this2 = this 239 | } 240 | } 241 | 242 | new Derived() 243 | console.log(this1 === this2) // true 244 | ``` 245 | 246 | This means that any assignment to `this` inside the parent class' `constructor` method also happens to `this` inside the derived class’ `constructor` method. We can see this below where we define a message on `this.hello` inside the parent class `Parent`. The message is then available on the `this` inside `Derived` after we call `super`. 247 | 248 | ```js 249 | class Parent { 250 | constructor() { 251 | this.hello = "Hello from Parent" 252 | } 253 | } 254 | 255 | class Derived extends Parent { 256 | constructor() { 257 | super() 258 | console.log(this.hello) // Hello from Parent 259 | } 260 | } 261 | 262 | new Derived() 263 | ``` 264 | 265 | >⚠️ Any use of `this` before calling `super` inside a derived class will result in a `ReferenceError`. 266 | > 267 | 268 | #### Arguments of a super call 269 | 270 | The arguments to a `super` call become the arguments used when [[Construct]] is called on the parent class. 271 | 272 | ```js 273 | class Calculator { 274 | constructor(num1, num2) { 275 | this.result = num1 + num2 276 | } 277 | } 278 | 279 | class Derived extends Calculator { 280 | constructor() { 281 | super(2, 6) 282 | console.log(this.result) // 8 283 | } 284 | } 285 | 286 | new Derived() 287 | ``` 288 | 289 | >💡 If you don’t define a `constructor` method inside a derived class, it’s the equivalent of defining one that calls `super` with all its own arguments (`constructor(...args) { super(...args) }`). 290 | > 291 | 292 | #### Accessing a property on super 293 | 294 | Accessing a property on `super` will go up the prototype chain, starting with the classes *parent*. This makes it useful if a method with the same name is defined in both a derived and parent class. We can see this below where we access `method` on both `this` and `super` with different results. 295 | 296 | ```js 297 | class Parent { 298 | method() { 299 | console.log("You accessed Parent") 300 | } 301 | } 302 | 303 | class Derived extends Parent { 304 | constructor() { 305 | super() 306 | this.method() // You accessed Derived 307 | super.method() // You accessed Parent 308 | } 309 | method() { 310 | console.log("You accessed Derived") 311 | } 312 | } 313 | 314 | new Derived() 315 | ``` 316 | 317 | # Private members 318 | 319 | Private members are exclusive to classes. They allow you to define properties and methods which are inaccessible outside their class. A private class member is prefixed with a hashtag (`#`). 320 | 321 | >💡 Private members won’t become part of the class's prototype chain inheritance. Meaning, they can’t be inherited by derived classes. 322 | > 323 | 324 | 325 | ### Private field 326 | 327 | ```js 328 | class Person { 329 | #age = 0 330 | constructor(age) { 331 | this.#age = age 332 | } 333 | } 334 | 335 | const person = new Person(30); 336 | person.#age // syntaxError 337 | ``` 338 | 339 | As we can see, `#age` is not accessible outside the class. 340 | 341 | >💡 Private properties have to be declared as fields. Meaning, they have to be declared in the body of the class (outside any method). This is unlike public properties, which can be declared directly on `this` without first being declared as a field. 342 | > 343 | 344 | ### Private method 345 | 346 | Like private fields, it’s also possible to define private methods. 347 | 348 | ```js 349 | class Person { 350 | #age = 0 351 | constructor(age) { 352 | this.#age = age 353 | } 354 | #canVote() { 355 | return this.#age >= 18 356 | } 357 | } 358 | 359 | const person = new Person(30) 360 | person.#canVote() // syntaxError 361 | ``` 362 | 363 | ### Private static members 364 | 365 | It’s also possible to define private static members. These fields and methods, while static, are only accessible inside the scope of the class they are defined within. 366 | 367 | ```js 368 | class Person { 369 | constructor() { 370 | Person.#incrementPeople() 371 | } 372 | static #totalPeople = 0 373 | static #incrementPeople() { 374 | Person.#totalPeople += 1 375 | } 376 | } 377 | 378 | Person.#incrementPeople() // SyntaxError 379 | Person.#totalPeople // SyntaxError 380 | ``` 381 | 382 | [^func-class-diff]: .*Almost* is an important keyword here. This great StackOverflow answer provides an in-depth explanation regarding whether classes are just syntactic sugar https://stackoverflow.com/a/54861781. The answer, no. 383 | -------------------------------------------------------------------------------- /images/car-constructor.drawio: -------------------------------------------------------------------------------- 1 | 7Vltb5swEP41kbYPjcCAIR/btFtVbVKlTmrzaXKwA2wOZo7ztl8/A3bCa5auoUm7SJGCH599xz135wN61nC6+sxREn5lmNAeMPCqZ133ADAN15V/KbLOEQs6ORDwCOeQsQUeot9Er1ToPMJkprAcEoxRESVl0GdxTHxRwhDnbFkWmzCKS0CCAlIyIwUefERJTewxwiLMUdstSN+SKAiVZttQE1OkZRUwCxFmywJk3fSsIWdM5FfT1ZDQ1Hdlt3xqmd3YxUks9llwdzvyvxnYezKC2aP7HS4v70YXnrJNrPX9EixvXw0ZFyELWIzozRa94mweY5LuasjRVuYLY4kETQn+IEKsFZdoLpiEQjGlanbCYqEmTVuOcxtSxa23pqAZm3Of7LgfHSGIB0TskAMbAmTgEjYlgq/lOk4oEtGibAdSERRs5LZelhfK0c9wutp3gehcaeoBSKW5VwknJTbgr3kaHpnHLmaZyy6lgGknq+2kvArS/yHieh9pVrZVPlHjuMzgMowEeUhQ5tilzOIKWxGlQ0YZz9ZakwmBvi/xmeDsJynMYHcwNoyNvgXhgqx2M1pnQC24ACBfsqkaaovlNgdNnVlhIf+g0RFpoDPS+glngol1Qtroy1EcLarQmFcRubRBLoP2MLHJwrOus66zrrOu7nS9udPJhIO/n06u84qnEzxGHye9xddP6fq+aToaGGWAYQ40cL1SKvLRuji6JzySHiBcgR22htZbaA2tbroMvUkq/O+7+IibhRzON6uldlvnclzbd1ajljtpK4hHrW8T0FLf4Bg68ED1DZTrG9D17mjdt3vk+uZ5XrW+bSROpL7Zb6G+2d3UN1mZwB4l6AVN0LvPeQDtE8v5wYtjBRzkPGncBXOZKx8+PvM4fFFUVEpHJUqwQzxsN0WJB8YWPFSUAK/c+Q6sWpQ4bj1IQFdBol8TF6JklpDMp+Ad+Nt0YF8/SOjEHNQTE9r9hqeN7pxefxmmne68A6eDgXmKTm8/O/cuZUZjQXTS3/3mJaRznf7aCluFXellUaawTE3MYlLhUUGIRkEsh75kJe2PrlLOIh/RSzUxjTDOGrmmmClHVTFsDhMDVqUFtoBTD4CG09DqjH7nTP/r0W/qc+1k6Icd0V//+rDHI/L/EgVG9SuU21UUyOH2U3A2V/iebt38AQ== -------------------------------------------------------------------------------- /images/car-constructor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carltheperson/advanced-js-objects/ffb99537d67f8d020c5f82feb677798f9a9a3aee/images/car-constructor.png -------------------------------------------------------------------------------- /images/class-func.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carltheperson/advanced-js-objects/ffb99537d67f8d020c5f82feb677798f9a9a3aee/images/class-func.png -------------------------------------------------------------------------------- /images/cover-big.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carltheperson/advanced-js-objects/ffb99537d67f8d020c5f82feb677798f9a9a3aee/images/cover-big.gif -------------------------------------------------------------------------------- /images/cover-small.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carltheperson/advanced-js-objects/ffb99537d67f8d020c5f82feb677798f9a9a3aee/images/cover-small.gif -------------------------------------------------------------------------------- /images/cover-src.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 | 12 |
13 |
14 | 15 |
16 |
17 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /images/date-instanceof.drawio: -------------------------------------------------------------------------------- 1 | 7Zhrb9owFIZ/TT62ShwS4GMLdJO2adPQtO2jiZ1L6+RkjsNlv342sZO4lDbTKJ2qSkjkvMeXg5/zBoLjz/LtO47L9BMQyhzkkq3jzx2EPG8SyDel7Bol9CeNkPCMNJLbCcvsN9UzjVpnhFZaayQBwERW2mIERUEjYWmYc9jYw2JgxBJKnFCrDCUsI8zowbDvGRFpo47GvdHvaZakeueRqxM5NmO1UKWYwKYn+QvHn3EA0Vzl2xll6uzsY7k5km3r4rQQQyYsRXD34Tb9doPj0ddgMcmBVxcaTiV25vNSIj++DoGLFBIoMFt06jWHuiBUrerKqBvzEaCUoifFWyrETrPEtQAppSJnOisL5rsfev4++KmCy8CE820/Od/pKIZC6EU9NbapXRV89Ei0VEHNI/rIOZjOwjyh4pFxqAUnG55CTmV9ch6nDItsbdeBdecl7biOjrzQgP4Cll53jVmtd3JQyGS51yWn8jJRlwQLamS5Sy9zgNoGuUkzQZcl3p/TRprZhhZnjM2AAd/P9eM4RlEk9UpwuKO9DAlXYRC2+60pF3T7OKDDA9UTjMv0vcMfh0286ZxopLRnwtB9JgKTl7BLv+3RadseDWx7/yXbHg1p+7ls+8uSgwCxK1+PARCaPukAzzunBcz38qvxgD/QA6OX9IA/xAOfV7fyF9ArdMEIhU+7wDjlLC4YDeFhJNW81pGHv2owiYtq39ZXcoAXlNsuaVYpalVOS7JZzN7gWQHT8Ajg8XTluqcB3D4UGMCT8QHgwD/ka6adnG94lO9gmOghmE6gXl9ahwZz9TpG9x5Heb7ChmVDKaCg9whqCbMsKWQYSR5U6teKViYfcq50Is8I2d+pH+oOu38e/hX+T/g9w7bF7x7gN1a28D+Xvadv+M+I3w+8/wu/d/xJ643/6fkHIToXfxl2/7jsc72/rfzFHw== -------------------------------------------------------------------------------- /images/date-instanceof.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carltheperson/advanced-js-objects/ffb99537d67f8d020c5f82feb677798f9a9a3aee/images/date-instanceof.png -------------------------------------------------------------------------------- /images/derived-class.drawio: -------------------------------------------------------------------------------- 1 | 7VnZctowFP0aHmEkLzJ+JGSdpikzPCTpm7CFUWMsV4gtX18Jy9hidUMgKc0Mw/geXS3cc490ZWp2ezi74TgdfGchiWsWCGc1+7JmWbbvym8FzDPAgnYGRJyGGQQKoEtfSQbCHB3TkIw0lkGCsVjQ1AQDliQkEAaGOWdT063P4tAAUhwRYxkK6AY4JmtujzQUgwx1vJL3LaHRQM/sAN0wxLmvBkYDHLJpCbKvanabMyayp+GsTWIVOjMs11tal+viJBFVOgDvGlzYXfuhedet91rP38L7Xl2TMxLz/PeSUP58bTIuBixiCY6vCvSCs3ESEjUqkFbhc89YKkEowV9EiLnmEo8Fk9BADGPdSmZUPJWen9VQDVdblzM98sKY50Yi+PypbJR6KbPotrDyfutR0oEbsTEPyI7Q5MmGeUTEDj8n81NxK02gObghbEjkeqQDJzEWdGKmFdbZGS39CgblgybxLwjV405wPNYz1SwUy/VfpJzIx0g9trHIUTlJqWEtG0yupwMqSDfFi7hNpdxNXvs0jtssZnzR1+73CQoCiY8EZy+k1BJ6fg/sZGdCuCCznfHMW23dRe8v0PEze1qoFek0H5SFah+LAWtPFN+imGX2Fwn/bOT75uzvs0ToQSEoKQ80HN8qqQ82AIC79SeNDuFUxojwg7XlVNRWfgh8EnE5FcXVSDkTTMxTcjYys9B+mUHrlDpD28kwgot+j9URu9BCfbQQQ0s6QJDOisacmc3MbeVNBk+Y5JgkJCwhK4xpCMc0SqQZSEqUoC4UFVSWHS3dMKRhuDhuN2WDmS/vsYv6aIVetL6LgnV2bXAkdptfhcm20Pj/5ubpV9k8Wwkd4vhsds3V4sSCzZMVJ5M2fw2S9AFH6Q+v87P++HLn1I9Sm4Qtdc8q9jaJXFO1rlKiryThG0tysDnalRNYd+0wKqdesmRbVsM1eUJew4UQWZ4PkO8hzzHHzLSnh1mhZLmuA0pIUF0rZ1lrgL2qOW2tAXdcq76KjUOLDQt+cLGRv5Lapre3EpwPopzVuyccmIPcknhCFBEburrq01kmiHupPkWaZEOa0+wR/tnkj201zWK1+dH5g3ZWqzqKB5+022/zLly5zS/tU9zmoVuxIv1kr8rcKqdscKJ3ZX1ry8GKeshF73SwNr2VQqe5fov3NinHOxYH3jEO1n2b539wxq7tkXDDa9H32SOlWfyLkZXAxT9B9tUf -------------------------------------------------------------------------------- /images/derived-class.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carltheperson/advanced-js-objects/ffb99537d67f8d020c5f82feb677798f9a9a3aee/images/derived-class.png -------------------------------------------------------------------------------- /images/memory-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carltheperson/advanced-js-objects/ffb99537d67f8d020c5f82feb677798f9a9a3aee/images/memory-1.png -------------------------------------------------------------------------------- /images/memory-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carltheperson/advanced-js-objects/ffb99537d67f8d020c5f82feb677798f9a9a3aee/images/memory-2.png -------------------------------------------------------------------------------- /images/memory.drawio: -------------------------------------------------------------------------------- 1 | 7VtLc5swEP41ProDEo/4mDhJe2innfrQ6VEGGZTIiAg5tvvrK4wwCENsJybGHnLIoNWT79vVSrt4AMfz1VeO4vAH8zEdAMNfDeD9AADLteT/VLDOBMC2M0HAiZ+JjEIwIf9wJjRz6YL4OFGyTCQYo4LEutBjUYQ9ockQ52ypN5sx6muCGAVYW0YqmHiI4p1mf4gvwvy1Sq2/YRKEambLUBVzlLdVgiREPluWRPBhAMecMZE9zVdjTFPodFgeG2q36+I4Egd1oI//hi/xMPbw48ideL//viRDNcorogv1vgPgUDneXcyxfAzSx5SKTChHL8nVe4l1jhVni8jH6XyGrF6GROBJjLy0dimVQ8pCMaeyZMrHGaF0zCjjm75wNpsBz5PyRHD2jEs1vjN1bGc73yvmAq8aQTC30EqNxGyOBV/LJqqDfZP12Cqjm5WXBbWO4isss+oohVLKFGwHLgCXDwrzI/AHNfhfHKgmcLuFKnxbq0voOi+L1P7uZiwSw2Sz+dzKBm68Kuq26u7e5cMkMYoKK9jaRp04kxkeRUmiTRnToWfuzkKSn9OnpjHTxb1vWMEXuGlU9/5q7NscOd1SResaDBw4drdQtRsNPDXkzWkgB6IwBDiSf6l73jFs1XeaC4yVUXZ50xpTz+ZpsBOJtNBp0+mJWIQrXCoRoiSIZNGTzGApv0t5I/I0cqsq5sT302lq9UTXpBbcJbQO4h0aLfHutM672fNe59HPTbzbOvGgJ77Of56b+JvWiYc98XUu/tzEm/YO2NiXN3NVVCjqODAuQhawCNHvjMWKkCcsxFrFFdBCsMp5SrKrKl1VLB2ilB7lCKcLeBtfuV624B5+48VGWTuBeIDFWwDU88UxRYK86us4OfqjRrPLblC79w4ehEM2TTB/JVEw3OhsstFZuWRZRUn0TGYE+0OP+bjZMk90s0pqrkCblzEPGvSg+fdclq5ma6gcAmEe0NqzM+QGfPqdYc/1XmfpPW6ichzQPcAFkR9w5BNc7GhqilOcE2DlgOjc7CiFVaMUwGpJKWru1dfhLgD8oB9QXX8xsjGEnEBbJxAaUB8i80+qV4Wc7TI+wNe+GN3FehjQe5ijd5PKdfPsLgbUhe0aXcyUH0lc9yN+tn4bsPLd4Wx5krqI38WhauZ61RlYmwNqB2ZKnC+gIVfihSgKUjpuN7mHq0oywI7RWBceuzhYgQM6Bmv7wac+zVDjbeDNYe6/tdgTaA5/9HmG0x78rG4xnw/cJxpaTzTAjjHf/ClUn2k4baYBdIz5aw0dQWVie1MN2S58rlwD/PRQECu+sDo2xNPnDz4cS3Ccg+y9teAOPCq40+cP2skfVCIh5m66+VPzB/Ba083Q+eDe3pQ/qBBojfQhWs4fwH2Rqy45DdA7jaM3iGqo9Oxeo/kTtN5rfJLXqHyj1KLTkMXilznZnlX8ugk+/Ac= -------------------------------------------------------------------------------- /images/optimized-cover.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carltheperson/advanced-js-objects/ffb99537d67f8d020c5f82feb677798f9a9a3aee/images/optimized-cover.gif -------------------------------------------------------------------------------- /images/person-class.drawio: -------------------------------------------------------------------------------- 1 | 7Vlbb5swFP41kbaHVmDAkMc27VZV21Spk9o+TQ52gI1gZpwL+/UzYAgmkKZrSJouUpTgz8c+5nznBhkYo+nyM0Ox/5ViEg6AhpcD42oAALRt8Z0BaQEY0CoAjwW4gLQVcB/8IQWol+gswCSRWAFxSkMexCro0igiLlcwxBhdqGITGmIFiJFHlGNkwL2LQrIm9hBg7heoadekb0jg+VKzqcmJKSplJZD4CNNFDTKuB8aIUcqLq+lyRMLMdKpZPnXMVudiJOLbLLi9eXK/a9h51Lzkwf4BFxe3T2eOPBtPy/slWNy+HFLGferRCIXXK/SS0VmESbarJkYrmS+UxgLUBfiTcJ5KLtGMUwH5fBrK2QmNuJzUTTEuzpAp7rw1CSV0xlyy4X5KD0HMI3yDHKgIEH5L6JRwlop1jISIB3P1HEh6kFfJrawsLqShX2B0ue8chTOpaQBgKI57GTOisAF/zzL3yC12luQmuxACuhkvV5Piyst+7whLaFRuJU6W71bMrdGskrjwA07uY5TbdiHiuEFYEIYjGlKWrzUmEwJdV+AJZ/QXqc1gezjWtErfnDBOlptJXSdBLijjpsobcofFKgpt6b5+LQCh1hNroE/WzmNGOeVpTLr4K1AczJvQmDURsbRFLoe2OGXbIU+6TrpOuk66+tN1dOVJh8Nn65NuW3ssUPAQvZywFksfs/Xnum6VwFMOaPqwBK6WUkUxSusjUQEDYQHCJNhje2gcQ3to9NNolJtkwv++S4xmYS2Gi83WQrurczns2Tdmo4476UqIB81vE9CR3+AYWnBH+Q2o+Q2U+a6e38oefS/5zT5wfnMcp5nfKok3kt/MY8hvZj/5jS5I58Pvbh6e3n3MA2i+sZgfvtpXwE7qSesuCUq/oSn58PGFBfFVftFIHg0/wRZxsNnmJw4YG3BXfgL0c2Cp7e/QWH890+IpoC9PKd8X11wlEuQorMrmpaLx6InQTUOUJTVmh+sxO9QquvbDRXeK3zritNa4tbLPXfWuzLrKPl3R16BXmJmrHKrcRDQiDSIlhMLAi8TQFbRkZfwyIy1wUXghJ6YBxnm/0eY0qlvV/WY3TmA0OjWj5LrmAbAlFI3e6LdO9O+Pfr3MvG+GftgT/esvybd4kvtfvEADqhdA+8BeYD9fj2XL/n7qMbCd/dZjMVz9g5zP1f6FN67/Ag== -------------------------------------------------------------------------------- /images/person-class.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carltheperson/advanced-js-objects/ffb99537d67f8d020c5f82feb677798f9a9a3aee/images/person-class.png -------------------------------------------------------------------------------- /images/prototype-chain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carltheperson/advanced-js-objects/ffb99537d67f8d020c5f82feb677798f9a9a3aee/images/prototype-chain.png -------------------------------------------------------------------------------- /images/prototype-function.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carltheperson/advanced-js-objects/ffb99537d67f8d020c5f82feb677798f9a9a3aee/images/prototype-function.png -------------------------------------------------------------------------------- /images/prototype-same.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carltheperson/advanced-js-objects/ffb99537d67f8d020c5f82feb677798f9a9a3aee/images/prototype-same.png -------------------------------------------------------------------------------- /images/prototype-unique.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carltheperson/advanced-js-objects/ffb99537d67f8d020c5f82feb677798f9a9a3aee/images/prototype-unique.png -------------------------------------------------------------------------------- /images/proxy-traps.drawio: -------------------------------------------------------------------------------- 1 | 7Vxbc5s4FP41fqwHdAH8mDpJ05l2m51kZrOPGGSbLUYuyLf++hWWMAiwTWMuHoeMJ0FHF4TO952LhDOA48X2S2gv59+pS/wB0NztAN4PADBMk/+OBTshgAYWglnouUKkpYIX7zcRQj2RrjyXRFImRIxSn3lLVejQICAOU2R2GNKN2mxKfVcRLO0ZUaYRC14c2yeFZv94LpsLKTIzrZ+IN5vLOyNNVizspK0URHPbpZuMCD4M4DiklImrxXZM/Hjp1GV5PFJ7mFdIAlalw2L892z31xN+efwWvb3+0p6+Rr8/QSgnx3bJAxOXP78s0pDN6YwGtv+QSj87q3BN4lF1XgjpKnD3JY2X0g7fKF3KJv8RxnZSs/aKUS6as4Uva8nWY2+Z63/joYYAy+L9Vg69L+ySQsDC3Vu2kO0Wl9N++1LScUoDJqeiG/ta9y5GCS8HNCBC8uj5vmwvlidek6PLLkURXYUOObHWCXrtcEbYiXbWARycU4QuCJ8/7xcS32beWp2HLdE9O7RLEcAvJAj+ABBy3LXtr+SdBsDwmVw5BSnGrxVNKj5F+zW94w10tNymlfxqtv+L488Xwn5sgueQLknIdgN8H3/k+Hy64hayQx6XKtA2c4+Rl6W9X/ENtzwqqKZcg2Pq03DfF7qYWC7i8oiF9CfJ1FhgAg3jcL81nxfZnlZ1UTVJBwuILtLUYX0kypvUcOjQELJ5xmhA0JQ2rTPL2PP1+OKBinzVYZeEBU0TlrOVUbZbkh/TWycsxHrHhIVaYRl7f9wQv1FVfmvlIGqH36hRfn+NHraMBJE34WPcGLuBrivsRhAPcZHfmtkiv3Gj2nz5WNZ6BDu21kaj2nwOyZovjSQoDaKbU6hhqQRN9gqyCsVam+4XX437TVys8KNmRferq+7XvMD9Or4dRZ6jeGC9Xg9sVfXAXTpgq+kA+4YzYqSrDO8+Ix41rc0bdsB5bV5BuqQXlrG3183Z62RP/qzBRl0a7GSWfcp0McOvIWVKsrg+Z6rDZHeeM+nNbljefNJUoGjnSVMS0x1xwnIbr3e0f+JoYUVHCzp1tLBA5deYJMdJpVcgVWbhUZFk0+kUOE4ZyVxjYuCaSAbR+UgXwTY5VsNWk1FqNeUok0TAneF2lzGXk3zLcyaUrzpTVaqqStqDrF6lyPa9WRATjGuJcPnnWIeeY/t3smLhue4+hC/DkGpg8jCqARQcFUkwlMBCRwVYGGWmV2sKFuZRWBz09ipsSKlGP4rqEDauS3HgPed8HR/t1ehCS4/2lLy8C2d68UH+vit/RnuXabCkXsCizMjPsSCDzeSNsYOvGWXhdb69iXNwFDNIwXl4lAvwWtxo6Q+aq77QBWrBVQEIMJ8JaLkhBDNkr5zBqgMToI/7azZVbSFFN4da5ic3oAB0c7j5OGlDyW4LAi2mDQD1YUZbYQaonLNfRZiReIuqYQaSuUazYUbx/L0PMyqHGWZFpBrtOBkLtxuOGH04UrdJMytCrx1EYU3vMmwp7rXcaNhSdqSA2jxRAMWvLfRhS1NhS1WOi4adhy2Wsttxtj1Ww5xmwpaECFfwGsp1grdGvFZ+B+Xi1/bfhVcMkYpXhE7itdDe0FrA63vSwh6vjb7jeum3Pt+HVgOo1hKYp9Gaby8PQZtFaw1nn5Z69qk5h0gqFULxU+GMdDgc9iekZYEkHOVOSDEqvj5W00EbL6ZfnhdYS/8BAXz4Hw== -------------------------------------------------------------------------------- /images/proxy-traps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carltheperson/advanced-js-objects/ffb99537d67f8d020c5f82feb677798f9a9a3aee/images/proxy-traps.png -------------------------------------------------------------------------------- /images/simple-cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carltheperson/advanced-js-objects/ffb99537d67f8d020c5f82feb677798f9a9a3aee/images/simple-cover.png -------------------------------------------------------------------------------- /images/strict-vs-sloppy-mode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carltheperson/advanced-js-objects/ffb99537d67f8d020c5f82feb677798f9a9a3aee/images/strict-vs-sloppy-mode.png --------------------------------------------------------------------------------