├── 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 |

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 | [](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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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
--------------------------------------------------------------------------------