December 18, 2025 Permalink
In a past life, I wrote an article called HTML is a Serialized Object Graph and That Changes Everything which made the case why understanding the declarative nature of HTML as an object graph which sits alongside JavaScript’s imperative DOM API is so crucial to becoming a well-rounded web developer who prioritizes performance, accessibility, efficiency, and future-proofing.
I want to continue unpacking some of those concepts, namely efficiency, because every so often I come across a troubling perspective: that Object-Oriented Programming (OOP) is somehow “bad” and therefore the DOM is bad because it’s based o…
December 18, 2025 Permalink
In a past life, I wrote an article called HTML is a Serialized Object Graph and That Changes Everything which made the case why understanding the declarative nature of HTML as an object graph which sits alongside JavaScript’s imperative DOM API is so crucial to becoming a well-rounded web developer who prioritizes performance, accessibility, efficiency, and future-proofing.
I want to continue unpacking some of those concepts, namely efficiency, because every so often I come across a troubling perspective: that Object-Oriented Programming (OOP) is somehow “bad” and therefore the DOM is bad because it’s based on OOP.
Let’s be very clear here, and I consider this statement to be wholly undeniable:
The Web Platform Would Not Exist Without OOP.
It’s not just that web APIs often take advantage of OOP. They would not exist without OOP! Virtually every web API you have ever used is designed specifically within the OOP paradigm. And every entity in the lifecycle of a web page is based on objects. These objects are instances of classes and in a great many cases these classes are subclasses of other classes. This means that key functionality is spread across a class hierarchy. That isn’t a bug, it’s a feature! And this means that it’s absurd to think that to write a web app you need to reach for a “framework”. The Web Platform is a framework.
Here’s a list of many of Web Platform classes you have used (or the libraries you import use):
Window– this is available as a “global”windowinstance which is a window (see what I did there?) into many other web APIs. In addition, when you directly callscreen,location,document, and some other global objects, you are actually accessing them viawindow. This is also true of a great many “functions”…for example, did you know thatsetTimeoutis actually a method of thewindowobject? (MDN Docs)Screen– this is a also a global instance which provides you with a number of properties describing the web browser’s display. (MDN Docs)Storage– when you access either thelocalStorageorsessionStorageproperty ofwindow, you are accessing an instance ofStorage. This is why the public API is the same for these two types of storage even though the internals (and thus behavior) are a bit different. (MDN Docs)Document– now here’s where things get even more interesting. When you access thedocumentproperty ofwindow, you are accessing the current document that’s being loaded and displayed (specifically anHTMLDocumentinstance). However, you can create new documents! It’s actually possible just to writeconst newDoc = new Document()and boom! You have a new instance of theDocumentclass, with all of the properties & methods thereof. (Typically you’ll create a document using a parser, such as the static class methodDocument.parseHTMLUnsafewhich returns a newHTMLDocumentinstance.) TheDocumentclass is itself a subclass ofNode. Every part of the DOM ultimately is a subclass (of a subclass, of a subclass) ofNode, such asElement,Comment,Attr, andText. ButNodeitself is actually a subclass ofEventTarget, which is the ultimate abstract superclass. EvenWindowis a subclass ofEventTarget. This is how (and why) the entire events system works in the browser! (MDN Docs)Request/Response– when you access thefetchmethod ofwindow, you probably provide a URL and perhaps some additional settings like the HTTP method, headers, a body payload, etc. But that’s simply shorthand for creating a new instance of theRequestclass. And you can actually do that!fetch(params)is the same asconst request = new Request(params); fetch(request). Which is pretty handy because that means you can passRequestobjects around, save them, modify them, and do whatever you want with them before passing them tofetch. The return value offetchis, well you guessed it! An instance of theResponseclass. When you call.json()or.text()after getting the response, those are methods onResponse. (MDN Docs)
If you’re ever wondering what the superclass is (if any) for a particular class, you can write Object.getPrototypeOf(ClassNameHere) in the console. All of the native HTML elements types, such as HTMLDialogElement, will be subclasses of HTMLElement. This is why when you write your own custom element, you write class MyWebComponent extends HTMLElement. You are subclassing the same superclass as all the other elements in HTML!
You can also monkey patch classes and objects. This is quite common in many OOP paradigms, but generally frowned upon in the context of web/browser APIs because of the fear of future breakage. Nevertheless, if you know what you’re doing and choose a suitable naming convention, I say go for it! For example, there’s an example on MDN of how to get a blob URL from a fetched image resource which you can then pass along to an img tag. Let’s monkey patch Response to make this a little easier!
// Monkey patch
Response.prototype.userlandBlobURL = async function() {
const blob = await this.blob()
return URL.createObjectURL(blob)
}
// Test it out!
const resp = await fetch("https://placehold.co/600x400")
const blobURL = await resp.userlandBlobURL()
// 'blob:https://example.com/bc10ebbc-d1bc-44fa-a23e-3bf398061502'
const img = document.createElement("img")
img.src = blobURL
document.body.append(img)
Perhaps JavaScript should have some kind of “blessed” naming convention for “custom methods” the way HTML does for custom elements (<you-must-use-dashes>). At any rate, I chose userland as a prefix in this example. YMMV!
To go back to the word I used above, efficiency, the beauty of the web platform is once you learn these class hierarchies and the various relationships between the objects in the framework that is built into the browser, you are well on your way to understanding how to build in a high-quality fashion. As I’ve mentioned many times, the reason you don’t want to spray “div tag soup” all over your users’ download web pages is because every tag is an object instance. Got 5 <div>s? That’s five HTMLDivElement object instances loaded into memory. Got 5000 <div>s? That’s five thousand HTMLDivElement object instances loaded into memory. Thankfully browsers are themselves marvels of efficiency, written in low-level C++, so even then it might not be the end of the world. But don’t be an asshole and assume your tab is the only tab running on a machine.
Imagine a world where every damn browser tab has an app loaded in it with an incredibly over-engineered and overwrought DOM littered with needless element instances in a sprawling in-memory object graph. Sadly we don’t need to imagine it because that world is already here. But that doesn’t mean we need to be OK with it. We can push back and fight for a more considerate world, one where you have much more functionality written into far fewer objects because you understand what these objects are and how they work.
Don’t be caught flat-footed the next time a frontend engineer asks you: “So, what are all the objects loaded into memory as your web application boots up? Describe them to me.” Oh dear, has no one ever asked you that before?!
🚨 Web dev education still has a l-o-o-ong way to go… 🫠🤪