If you are starting out as a developer, you might run into these conflicting schools of thought: should I write "functional style" with its "pure functions" or go with "object oriented classes"?
The answer would be really long and this is going to be just a quick tip.
Beforehand it is important to know that Javascript (and consequentially TypeScript) is neither functional nor an object oriented language. It has elements of both and it breaks important contracts of either. See the last paragraph: "Only for the curious!".
If you are going to have several similar objects that persist over time (i.e. you keep them around for a long time and you regularly) and have some form of state that you access a
class
is a really good choice. However if you just need to transform an object to another object afunction
is your best bet.
The symptoms of not needing a class
The feature a class
is truly giving us is accessing this
in any of the defined functions
. When some or more of your class methods do not access this
and only the parameters passed and they also return the result, then you will need a function
. Even better if you can make the function
pure (in other words none of the parameters passed in are mutated)...
The algorithm to refactor this
- Find a class method that do not access
this
. Extract it into a method and update the depending methods. - Check the class again, if the changed methods no longer access
this
: move them out as well. - If every method access
this
you have the real class that is actually managing its own state.
Only for the curious
The classic example of breaking the functional paradigm is that on an array .sort()
method mutates the array, while .map()
returns you a new array instance. This is inconsistent and creates regular problems.
On the other hand objects created from traditional classes "own" their own functions, while in Javascript you can pass the function
of an object
and lose its this
. You can also bind a function
to a totally different object
. There is a lot of frustration voiced when this phenomenon dawns on a developer coming from real object oriented background.
Questions, mistakes or do you wish examples? Blogging and learning is a collaborative effort, I need your help to help you. 🤝 Let me know in the comments! Cheers!
Top comments (7)
I agree with most of this (particularly the relative verbosity of class vs prototype), though I would argue that classes in JS are still the better way to do OOP. OOP isn't just about encapsulation, it's about polymorphism and data transmission just as well. All of those are certainly helped by a clear class structure over module-based encapsulation.
class
keyword or not - the objects instantiated still rely on prototypal inheritance. And in performance conscious code bases constructor functions are still "assembled" - that way method implementations can be shared by otherwise unrelated constructor functions (see Preact).Class free OOP doesn't relate to implementing inheritance with prototypes. "OOP with functions" typically refers to a plain literal object holding references to functions that share data through the closure of the factory function that created them.
OOP with functions in JavaScript
How to decide between classes v. closures in JavaScript.
Keeping in mind that in JavaScript Classes are a template for creating objects - for the most part they are just a creational convenience (until more is needed) but they are not the only way to create objects in JavaScript; unlike in mainstream class-based object-oriented languages.
James Coplien
"How many of you do object-oriented programming? What language? Java is the only language in which you cannot do object-oriented programming. Huh? Other languages. Smalltalk - no. C# - no. Python - no. No. Any JavaScript programmers here? Here are my object-oriented programmers - they're writing objects - the rest of you are writing classes. "
JavaScript has always achieved polymorphism through "duck typing" - no classes required. TypeScript's structural typing is an expression of that. Either a value satisfies an interface or type alias or it does not - no need to
implement
,inherit
orextend
anything.More often than not I hear people complaining about brittle, deep, complex class hierarchies.
"Favor object composition over class inheritance"
Gamma, Erich et al. "Design Patterns Elements of Reusable Object-Oriented Software". Putting Resuse Mechanisms to Work, p.20, 1994
So a "clear class structure" doesn't seem to be a pit of success for OOD/P in practice.
I think that is a red herring. I myself have described JavaScript as function-oriented but using an FP-flavoured expression-based (as opposed to statement-based) style leads more to value-oriented programming.
Imho I prefer the functional programming over OOP.
Because
It depends very much on the programming paradigm you apply. Traditionally OOP applications benefit from classes in JS, but is that the kind of JS we want to write? Do we prefer functional programming, or do we want to go back to using the actual design of the language and relying on prototypes?
Functional programming is certainly seeing a new renaissance in JS, but rarely in the terms that are actually related to traditional functional programming (pure functions, higher-order-functions, declarative programming). Prototype-based programming is all but gone, save for polyfills. It's certainly an interesting development.
Prototype-based programming is truly gone, I only realized it when I failed an interview question about prototypal inheritance. "Well" I said "I haven't written anything prototype related in the last 4 years."
Onto the other approaches: since the language was not designed for either, doing them as they were written requires ugly hacks and clunky solutions. For the love of god I despise for example when I see a trainwreck like
myThing(354)('adf')((_, stuff) => stuff.process())
. If you stick to the basic rule of being as clear as possible with the least amount of boilerplate you won't use anonymous function compositions nor you won't fire up a whole dependency injection system just you can mock two dependencies for a test.@lukeshiru
I know you're going to react to this and I want to learn more.