|
| 1 | +# About |
| 2 | + |
| 3 | +JavaScript includes the capabilities for object-oriented programming ([OOP][wiki-oop]). |
| 4 | +In OOP, you want to create objects (_instances_) from "templates" (_classes_) so that they include certain data and functionality. |
| 5 | +The data properties are called _fields_ in the OOP context, the function properties are called _methods_. |
| 6 | + |
| 7 | +JavaScript did not have classes at all before they were added to the language specification in 2015, but allowed for object-oriented programming using prototype-based inheritance. |
| 8 | +And even though a `class` keyword is available nowadays, JavaScript is still a _prototype-based_ language. |
| 9 | + |
| 10 | +To understand what it means to be a prototype-based language and how JavaScript actually works, we will go back to the time when there were no classes. |
| 11 | + |
| 12 | +## Prototype Syntax |
| 13 | + |
| 14 | +### Constructor Function |
| 15 | + |
| 16 | +In JavaScript, the template (class) is facilitated by a regular function. |
| 17 | +When a function is supposed to be used as such a template, it is called a _constructor function_ and the convention is that the function name should start with a capital letter. |
| 18 | +Instances (objects) are derived from the template using the `new` keyword when invoking the constructor function. |
| 19 | + |
| 20 | +```javascript |
| 21 | +function Car() { |
| 22 | + // ... |
| 23 | +} |
| 24 | + |
| 25 | +const myCar = new Car(); |
| 26 | +const yourCar = new Car(); |
| 27 | +``` |
| 28 | + |
| 29 | +It is important to note that in JavaScript, the instances and the constructor function keep a relationship to each other even after the instances were created. |
| 30 | +Every instance object includes a hidden, internal property referred to as `[[prototype]]` in the language specification. |
| 31 | +It holds a reference to the value of the `prototype` key of the constructor function. |
| 32 | +Yes, you read that correctly, a JavaScript function can have key/value pairs because it is also an object behind the scenes. |
| 33 | + |
| 34 | +Since 2015, `[[prototype]]` can be accessed via `Object.getPrototypeOf()`. |
| 35 | +Before that, it was accessible via the key `__proto__` in many environments. |
| 36 | + |
| 37 | +Do not confuse the prototype of an object (`[[prototype]]`) with the `prototype` property of the constructor function. |
| 38 | + |
| 39 | +```exercism/note |
| 40 | +To summarize: |
| 41 | +
|
| 42 | +- Constructors in JavaScript are regular functions. |
| 43 | +- Constructing a new instance creates an object with a relation to its constructor called its _prototype_. |
| 44 | +- Functions are objects (callable objects) and therefore they can have properties. |
| 45 | +- The constructor's (function) `prototype` property will become the instance's _prototype_. |
| 46 | +``` |
| 47 | + |
| 48 | +### Instance Fields |
| 49 | + |
| 50 | +Often, you want all the derived objects (instances) to include some fields and pass some initial values for those when the object is constructed. |
| 51 | +This can be facilitated via the [`this` keyword][mdn-this]. |
| 52 | +Inside the constructor function, `this` represents the new object that will be created via `new`. |
| 53 | +`this` is automatically returned from the constructor function when it is called with `new`. |
| 54 | + |
| 55 | +That means we can add fields to the new instance by adding them to `this` in the constructor function. |
| 56 | + |
| 57 | +```javascript |
| 58 | +function Car(color, weight) { |
| 59 | + this.color = color; |
| 60 | + this.weight = weight; |
| 61 | + this.engineRunning = false; |
| 62 | +} |
| 63 | + |
| 64 | +const myCar = new Car('red', '2mt'); |
| 65 | +myCar.color; |
| 66 | +// => 'red' |
| 67 | +myCar.engineRunning; |
| 68 | +// => false |
| 69 | +``` |
| 70 | + |
| 71 | +### Instance Methods |
| 72 | + |
| 73 | +Methods are added via the `prototype` property of the constructor function. |
| 74 | +Inside a method, you can access the fields of the instance via `this`. |
| 75 | +This works because of the following general rule. |
| 76 | + |
| 77 | +> When a function is called as a method of an object, its `this` is set to the object the method is called on. [^1] |
| 78 | +
|
| 79 | +```javascript |
| 80 | +function Car() { |
| 81 | + this.engineRunning = false; |
| 82 | + // ... |
| 83 | +} |
| 84 | + |
| 85 | +Car.prototype.startEngine = function () { |
| 86 | + this.engineRunning = true; |
| 87 | +}; |
| 88 | + |
| 89 | +Car.prototype.addGas = function (litre) { |
| 90 | + // ... |
| 91 | +}; |
| 92 | + |
| 93 | +const myCar = new Car(); |
| 94 | +myCar.startEngine(); |
| 95 | +myCar.engineRunning; |
| 96 | +// => true |
| 97 | +``` |
| 98 | + |
| 99 | +### The Prototype Chain |
| 100 | + |
| 101 | +`myCar` in the example above is a regular JavaScript object and if we would inspect it (e.g. in the browser console), we would not find a property `startEngine` with a function as value directly inside the `myCar` object. |
| 102 | +So how does the code above even work then? |
| 103 | + |
| 104 | +The secret here is called the _prototype chain_. |
| 105 | +When you try to access any property (field or method) of an object, JavaScript first checks whether the respective key can be found directly in the object itself. |
| 106 | +If not, it continues to look for the key in the object referenced by the `[[prototype]]` property of the original object. |
| 107 | +As mentioned before, in our example `[[prototype]]` points to the `prototype` property of the constructor function. |
| 108 | +That is where JavaScript would find the `startEngine` function because we added it there. |
| 109 | + |
| 110 | +```javascript |
| 111 | +function Car() { |
| 112 | + // ... |
| 113 | +} |
| 114 | + |
| 115 | +Car.prototype.startEngine = function () { |
| 116 | + // ... |
| 117 | +}; |
| 118 | +``` |
| 119 | + |
| 120 | +And the chain does not end there. |
| 121 | +The `[[prototype]]` property of `Car.prototype` (`myCar.[[prototype]].[[prototype]]`) references `Object.prototype` (the `prototype` property of the `Object` constructor function). |
| 122 | +It contains general methods that are available for all JavaScript objects, e.g. `toString()`. |
| 123 | +The `[[prototype]]` of `Object` is usually `null` so the prototype chain ends there. |
| 124 | +In conclusion, you can call `myCar.toString()` and that method will exist because JavaScript searches for that method throughout the whole prototype chain. |
| 125 | +You can find a detailed example in the [MDN article "Inheritance and the prototype chain"][mdn-prototype-chain-example]. |
| 126 | + |
| 127 | +```exercism/caution |
| 128 | +Note that the prototype chain is only travelled when retrieving a value. |
| 129 | +Setting a property directly or deleting a property of an instance object only targets that specific instance. |
| 130 | +This might not be what you would expect when you are used to a language with class-based inheritance. |
| 131 | +``` |
| 132 | + |
| 133 | +### Dynamic Methods (Adding Methods to All Existing Instances) |
| 134 | + |
| 135 | +JavaScript allows you to add methods to all existing instances even after they were created. |
| 136 | + |
| 137 | +We learned that every instance keeps a reference to the `prototype` property of the constructor function. |
| 138 | +That means if you add an entry to that `prototype` object, that new entry (e.g. a new method) is immediately available to all instances created from that constructor function. |
| 139 | + |
| 140 | +```javascript |
| 141 | +function Car() { |
| 142 | + this.engineRunning = false; |
| 143 | +} |
| 144 | + |
| 145 | +const myCar = new Car(); |
| 146 | +// Calling myCar.startEngine() here would result in "TypeError: |
| 147 | +// myCar.startEngine is not a function". |
| 148 | + |
| 149 | +Car.prototype.startEngine = function () { |
| 150 | + this.engineRunning = true; |
| 151 | +}; |
| 152 | + |
| 153 | +myCar.startEngine(); |
| 154 | +// This works, even though myCar was created before the method |
| 155 | +// was added. |
| 156 | +``` |
| 157 | + |
| 158 | +In theory, dynamic methods can even be used to extend the functionality of built-in objects like `Object` or `Array` by modifying their prototype. |
| 159 | +This is called _monkey patching_. |
| 160 | +Because this change affects the whole application, it should be avoided to prevent unintended side effects. |
| 161 | +The only reasonable use case is to provide a [polyfill][wiki-polyfill] for a missing method in older environments. |
| 162 | + |
| 163 | +## Class Syntax |
| 164 | + |
| 165 | +Nowadays, JavaScript supports defining classes with a `class` keyword. |
| 166 | +This was added to the language specification in 2015. |
| 167 | +On the one hand, this provides syntactic sugar that makes classes easier to read and write. |
| 168 | +The new syntax is more similar to how classes are written in languages like C++ or Java. |
| 169 | +Developers switching over from those languages have an easier time to adapt. |
| 170 | +On the other hand, class syntax paves the way for new language features that are not available in the prototype syntax. |
| 171 | + |
| 172 | +### Class Declarations |
| 173 | + |
| 174 | +With the new syntax, classes are defined with the `class` keyword, followed by the name of the class and the class body in curly brackets. |
| 175 | +The body contains the definition of the constructor function, i.e. a special method with the name `constructor`. |
| 176 | +This function works just like the constructor function in the prototype syntax. |
| 177 | +The class body also contains all method definitions. |
| 178 | +The syntax for the methods is similar to the shorthand notation we have seen for adding functions as values inside an object, see [Concept Objects][concept-objects]. |
| 179 | + |
| 180 | +```javascript |
| 181 | +class Car { |
| 182 | + constructor(color, weight) { |
| 183 | + this.color = color; |
| 184 | + this.weight = weight; |
| 185 | + this.engineRunning = false; |
| 186 | + } |
| 187 | + |
| 188 | + startEngine() { |
| 189 | + this.engineRunning = true; |
| 190 | + } |
| 191 | + |
| 192 | + addGas(litre) { |
| 193 | + // ... |
| 194 | + } |
| 195 | +} |
| 196 | + |
| 197 | +const myCar = new Car(); |
| 198 | +myCar.startEngine(); |
| 199 | +myCar.engineRunning; |
| 200 | +// => true |
| 201 | +``` |
| 202 | + |
| 203 | +Similar to function declarations and function expressions, JavaScript also supports [class expressions][mdn-class-expression] in addition to the _class declaration_ shown above. |
| 204 | + |
| 205 | +Keep in mind that behind the scenes, JavaScript is still a prototype-based language. |
| 206 | +All the mechanisms we learned about in the "Prototype Syntax" section above still apply. |
| 207 | + |
| 208 | +### Private Fields, Getters and Setters |
| 209 | + |
| 210 | +By default, all instance fields are public in JavaScript. |
| 211 | +They can be directly accessed and assigned to. |
| 212 | + |
| 213 | +Adding actual private fields to the language specification is work in progress, see the [proposal document][proposal-private-fields] for details. |
| 214 | + |
| 215 | +In the meantime, you can make use of the established convention that fields and methods that start with an underscore should be treated as private. |
| 216 | +They should never be accessed directly from outside the class. |
| 217 | + |
| 218 | +Private fields are sometimes accompanied by [getters][mdn-get] and [setters][mdn-set]. |
| 219 | +With the keywords `get` and `set` you can define functions that are executed when a property with the same name as the function is accessed or assigned to. |
| 220 | + |
| 221 | +```javascript |
| 222 | +class Car { |
| 223 | + constructor() { |
| 224 | + this._milage = 0; |
| 225 | + } |
| 226 | + |
| 227 | + get milage() { |
| 228 | + return this._milage; |
| 229 | + } |
| 230 | + |
| 231 | + set milage(value) { |
| 232 | + throw new Error(`Milage cannot be manipulated, ${value} is ignored.`); |
| 233 | + // Just an example, usually you would not provide a setter in this case. |
| 234 | + } |
| 235 | +} |
| 236 | + |
| 237 | +const myCar = new Car(); |
| 238 | +myCar.milage; |
| 239 | +// => 0 |
| 240 | +myCar.milage = 100; |
| 241 | +// => Error: Milage cannot be manipulated, 100 is ignored. |
| 242 | +``` |
| 243 | + |
| 244 | +### Class Fields and Class Methods |
| 245 | + |
| 246 | +In OOP, you sometimes want to provide utility fields or methods that do not depend on the specific instance. |
| 247 | +Instead, they are defined for the class itself. |
| 248 | +This can be achieved with the `static` keyword. |
| 249 | + |
| 250 | +```javascript |
| 251 | +class Car { |
| 252 | + static type = 'vehicle'; |
| 253 | + |
| 254 | + static isType(targetType) { |
| 255 | + return targetType === 'vehicle'; |
| 256 | + } |
| 257 | +} |
| 258 | + |
| 259 | +Car.type; |
| 260 | +// => 'vehicle' |
| 261 | + |
| 262 | +Car.isType('road sign'); |
| 263 | +// => false |
| 264 | +``` |
| 265 | + |
| 266 | +### Class-Based Inheritance |
| 267 | + |
| 268 | +Besides the type of [inheritance][wiki-inheritance] along the prototype chain we saw earlier, you can also represent inheritance between classes in JavaScript. |
| 269 | +This is covered in the [Concept Inheritance][concept-inheritance]. |
| 270 | + |
| 271 | +--- |
| 272 | + |
| 273 | +[^1]: `this` Examples - As an object method, MDN. <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this#as_an_object_method> (referenced December 03, 2021) |
| 274 | + |
| 275 | +[wiki-oop]: https://en.wikipedia.org/wiki/Object-oriented_programming |
| 276 | +[mdn-prototype-chain-example]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain#inheritance_with_the_prototype_chain |
| 277 | +[concept-inheritance]: /tracks/javascript/concepts/inheritance |
| 278 | +[mdn-class-expression]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes#Class_expressions |
| 279 | +[wiki-inheritance]: https://en.wikipedia.org/wiki/Inheritance_(object-oriented_programming) |
| 280 | +[proposal-private-fields]: https://github.com/tc39/proposal-private-methods#private-methods-and-fields |
| 281 | +[mdn-get]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get |
| 282 | +[mdn-set]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/set |
| 283 | +[wiki-polyfill]: https://en.wikipedia.org/wiki/Polyfill_(programming) |
| 284 | +[mdn-this]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this |
| 285 | +[concept-objects]: /tracks/javascript/concepts/objects |
0 commit comments