Dialects of JavaScript: Classes and Functions

JavaScript often leads in surveys for most developer interest and most employer need. It has gone from a once peripheral necessity of webpages to the most dynamic and hotly debated programming language. With so much change, one company’s JavaScript might be fairly alien to developers from another organization. Like different English speaking countries, it could be said that JavaScript developers are becoming divided by a common language. Technology trends such as React, Angular, TypeScript, as well as new and upcoming language features, fuel the movements. At the same time, JavaScript developers have always tended towards a pastoral simplicity in approaches in contrast to the more clerical bookkeeping of Enterprise server programming.

Functional Or Class Based

JavaScript was built around the notion of first class functions, whereby functions can be passed as arguments as easily as anything else. For some other languages, this behavior has been added or expanded on over time. Likewise, JavaScript which began with a strictly prototypal inheritance has now introduced classes. TypeScript behaves like a full-fledged OO language with generics, although this reality fades after compilation.

The result is something of a split personality. In the world of web single page applications, leading frameworks provide an authoring workflow which make day to day coding a bit more removed from low level language decisions. Even so, the functional versus class paradigm fight manifests itself in interest ways, and in the most popular framework, React, the choice between class-based components and functional components became a center stage topic.

Should We Avoid Classes?

React development is taking a leaning toward the functional in component definition. It would be dangerous to generalize this as a move away from classes. JavaScript has dipped its toes in class inheritance before such as in CoffeeScript, and TypeScript is become extremely popular. Even more tellingly, in the absence of the soon-to-come private modifier, JavaScript developers have gone to awkward lengths to use closure scope to mimic the encapsulation benefits of class based access modifiers. React makes functional components deceptively simple with the use of hooks, a deus ex machina hiding lifecycle complexity in a convention which, owing to framework popularity, everyone has been willing and eager to learn. The same mechanism added to an in-house codebase from scratch may well hamper effective onboarding.

Class based inheritance performs best at organizing large codebases. In simple applications, code organization is less of a problem, but as a codebase grows, the effort of maintaining and enhancing a codebase can be a direct function of its size. Front-end codebases are increasing a place where code complexity resides, with business logic creeping forward to an extent once avoided. Composition over inheritance is a timeless debate, but if Design Patterns advocated a move towards the former, it also showed eloquent examples of the latter doing what it does best, such as the template pattern. In this, a base class, often abstract, will have a generalized piece of code flow, leaving out some fillings which must be provided by the inheriting classes. Of course, there are similar strategies employed in functional programming, but that piece of DRYness conveys its meaning best in the language of the class hierarchy.

How To Decide?

Language decisions should look beyond the grammar of the sentence and into the flow of the story. When using a framework, the JavaScript will do well to adhere to expectations within that framework, not least because it will allow an experienced developer to find their way around. Beyond the framework, it’s worth asking what distinguishes ones own product or use case from others, and especially to think how extensibility and maintainability will be impacted by decisions.

Larger choices can flow into smaller choices. If one is using the language more functionally, question usages of the “this” keyword and consider first whether state is desirable, and then if something needs to be stateful, exactly what should be stateful. Global state mechanisms and React’s hooks are examples of mechanisms which help move away from traditional storage of state. 

Conversely, if one is relying upon a strong association between method code and the class in which it is declared, remember that arrow functions guarantee a bond with the intuitive “this.” Declaring a method inside a class but allowing it to be called with a different “this” becomes intuitively confusing. TypeScript provides more nuanced mechanisms to do this while avoiding instances possessing their own copy of a function in memory, at the expense of requiring the reader to know TypeScript details.

Conclusion

  • Make code pattern decisions purposefully to help your needs.

  • Share the patterns and their reasons in documentation.

  • Where applicable, use linter rules to apply a writing style.


Julian Flaks