Testing Auth in Playwright
Jun 6, 2024
ES6 Classes in Playwright ECMAScript
Over the course of my work with node-based projects, I keep finding myself interally cringing when encountering classes in Javascript/Typescript. Perhaps my gut reaction is because I came into professional programming by being the only person with the time and the willingness to maintain the work. My introduction was heavily JS influenced, and classes were just a buzzword until I was a few years into my career.
I appreciate and take full advantage of encapsulation and dependency injection, but as I’ve branched into real OO languages (Java, C#), I’ve found myself making assumptions when having to work in JS classes that have come back to bite me. Whenver I see a class in a JS/TS project, I can’t help but think “Why not just use a function?”. Composed, type-protected factory functions and closures feel like they can do everything a class can do, but with less overhead and less cognitive load.
The Good…
When the domain is simple and nobody succumbs to the temptation to over-engineer, classes can get the job done and send everyone home happy. Maybe your team got fancy and did a bit of abstract definitions and interfaces, but the code is still readable and maintainable.
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a noise.`);
}
}
class Dog extends Animal {
constructor(name) {
super(name);
}
speak() {
console.log(`${this.name} barks.`);
}
}
const a = new Animal('Animal');
a.speak(); // Animal makes a noise.
const d = new Dog('Mitzie');
d.speak(); // Mitzie barks.
The Bad…
As best as I’ve been able to tell, the entire reason for explicit class syntax being introduced to the ECMAScript standard was to make it easier for people coming from other OO languages to contribute to front-end projects. Like ENUMS, classes are a syntax feature that was bolted on to the language to make it more palatable to developers from ‘real’ OO languages.
But in the JS world, prototypes are the real deal. Dressing them up in OO languages cannot change how the language actually works, and the existing implementation of classes can be a bit of a minefield. I will now shamelessly crib from one of my preferred articles on the topic
JavaScript Pop Quiz: What Does the Following Code Do?
function Proto() {
this.name = 'Proto'
return this;
}
Proto.prototype.getName = function () {
return this.name
}
class MyClass extends Proto {
constructor() {
super()
this.name = 'MyClass'
}
}
const instance = new MyClass()
console.log(instance.getName())
Proto.prototype.getName = function () {
return 'Overridden in Proto'
}
console.log(instance.getName())
MyClass.prototype.getName = function () {
return 'Overridden in MyClass'
}
console.log(instance.getName())
instance.getName = function () {
return 'Overridden in instance'
}
console.log(instance.getName())
The correct answer is that it prints to console:
> MyClass
> Overridden in Proto
> Overridden in MyClass
> Overridden in instance
If you answered incorrectly, you don’t understand what class actually is. This isn’t your fault. Much like Array, class is not a language feature, it’s syntactic obscurantism. It tries to hide the prototypical inheritance model and the clumsy idioms that come with it, and it implies that JavaScript is doing something that it is not.
I had burned myself several times without really knowing why when I first read this article. I find that it’s always cathartic to have someone you’ve never met explain why you’ve been feeling a certain way about a topic. As I’ve done work in both class-y and class-less projects, I’ve found that the class-less projects are easier to maintain and refactor. I’ve also found that the class-y projects encounter more issues as a result of their syntax misleading the developers into thinking they’re working in a language paradigm that they’re not. Everything in JS is an object, and the class syntax is just a way to make it look like the class definitions are immutable.
In the abstract, using classes is fine. But I feel that if you want to use JS/TS professionally, you are better served by taking the time to understand how the language actually works and, having done so, the appeal of classes will diminish. At the end of the day, the ultimate goal is codifying your intent as clearly as possible. Classes necessarily obfuscate certain aspects of the language, and I find that the more I work with them, the more I find myself fighting the constructs to get them to do what I want.