Skip to content

Commit b65b981

Browse files
authored
Add Concept Exercise: Windowing System (Classes) (#1476)
1 parent 4583f0f commit b65b981

File tree

19 files changed

+1389
-2
lines changed

19 files changed

+1389
-2
lines changed

concepts/classes/.meta/config.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"blurb": "JavaScript allows for object-oriented programming. Despite it having a \"class\" keyword, it is still a prototype-based language.",
3+
"authors": ["junedev"],
4+
"contributors": []
5+
}

concepts/classes/about.md

Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
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

Comments
 (0)