Skip to content

Commit 0858f21

Browse files
committed
objects-classes, ch3: added opinionated and somewhat disputed 'avoid this' section
1 parent 1d2aa0d commit 0858f21

File tree

1 file changed

+71
-1
lines changed

1 file changed

+71
-1
lines changed

objects-classes/ch3.md

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
| :--- |
66
| Work in progress |
77

8-
The class-design pattern generally entails defining a general definition for a *type of thing* (class), including data (members) and behaviors (methods), and then creating one or more concrete *instances* of this class definition as actual objects that can interact and perform tasks. Moreover, class-orientation allows declaring a relationship between two or more classes, through what's called "inheritance", to derive new and augmented "subclasses" that mix-n-match and even re-define behaviors.
8+
The class-design pattern generally entails defining a definition for a *type of thing* (class), including data (members) and behaviors (methods), and then creating one or more concrete *instances* of this class definition as actual objects that can interact and perform tasks. Moreover, class-orientation allows declaring a relationship between two or more classes, through what's called "inheritance", to derive new and augmented "subclasses" that mix-n-match and even re-define behaviors.
99

1010
Prior to ES6 (2015), JS developers mimicked aspects of class-oriented (aka "object-oriented") design using plain functions and objects, along with the `[[Prototype]]` mechanism (as explained in the previous chapter) -- so called "prototypal classes".
1111

@@ -288,6 +288,76 @@ var thing = new SomethingCool();
288288
thing.DEFAULT_GREETING; // Hello!
289289
```
290290

291+
#### Avoid This
292+
293+
One pattern that has emerged and grown quite popular, but which I firmly believe is an anti-pattern for `class`, looks like the following:
294+
295+
```js
296+
class SomethingCool {
297+
number = 21
298+
getNumber = () => this.number * 2
299+
300+
speak() { /* .. */ }
301+
}
302+
303+
var another = new SomethingCool();
304+
305+
another.getNumber(); // 42
306+
```
307+
308+
See the field holding an `=>` arrow function? I say this is a no-no. But why? Let's unwind what's going on.
309+
310+
First, why do this? Because JS developers seem to be perpetually frustrated by the dynamic `this` binding rules (see a subsequent chapter), so they force a `this` binding via the `=>` arrow function. That way, no matter how `getNumber()` is invoked, it's always `this`-locked to the particular instance. That's an understandable convenience to desire, but... it betrays the very nature of the `this` / `[[Prototype]]` pillar of the language. How?
311+
312+
Let's consider the equivalent code to the previous snippet:
313+
314+
```js
315+
class SomethingCool {
316+
constructor() {
317+
this.number = 21;
318+
this.getNumber = () => this.number * 2;
319+
}
320+
321+
speak() { /* .. */ }
322+
}
323+
324+
var another = new SomethingCool();
325+
326+
another.getNumber(); // 42
327+
```
328+
329+
Can you spot the problem? Look closely. I'll wait.
330+
331+
...
332+
333+
We've made it clear repeatedly so far that `class` definitions put their methods on the class constructor's `prototype` object, such that there's just one function and it's inherited (shared) by all instances. That's what will happen with `speak()` in the above snippet.
334+
335+
But what about `getNumber()`? That's essentially a class method, but it won't be handled by JS quite the same as `speak()` will. Consider:
336+
337+
```js
338+
Object.hasOwn(another,"number"); // true
339+
Object.hasOwn(another,"speak"); // false
340+
Object.hasOwn(another,"getNumber"); // true -- oops!
341+
```
342+
343+
You see? By defining a function value and attaching it as a field/member property, we're losing the shared prototypal method'ness of the function, and it's becoming just like any per-instance property. That means we're creating a new function property **for each instance**, rather than it being created just once on the class constructor's `prototype`.
344+
345+
That's wasteful in performance and memory, even if by a tiny bit. That alone should be enough to avoid it.
346+
347+
But I would argue that way more importantly, what you've done with this pattern is invalidate the very reason why using `class` and `this`-aware methods is even remotely useful/powerful!
348+
349+
If you go to all the trouble to define class methods with `this.` references throughout them, but then you lock/bind all those methods to a specific object instance, you've basically travelled all the way around the world just to go next door.
350+
351+
If all you want is function(s) that are statically fixed to a particular "context", and don't need any dynamicism or sharing, what you want is... **closure**. And you're in luck: I wrote a whole book in this series on how to use closure so functions remember/access their statically defined scope (aka "context"). That's a way more appropriate, and simpler to code, way to get what you're doing.
352+
353+
Don't abuse/misuse `class` and turn it into a over-hyped, glorified collection of closure.
354+
355+
I'm *not* saying: "never use `=>` arrow functions inside classes".
356+
357+
I *am* saying: "never attach an `=>` arrow function as an instance property in place of a dynamic prototypal class method, either out of mindless habit, or laziness in typing fewer characters, or misguided `this`-binding convenience."
358+
359+
In a subsequent chapter, we'll dive deep into how to understand and properly leverage the full power of the dynamic `this` mechanism.
360+
291361
## Class Extension
292362

293363
The way to unlock the power of class inheritance is through the `extends` keyword, which defines a relationship between two classes:

0 commit comments

Comments
 (0)