Stop ignoring the DOM: It’s the only thing that actually matters
6 mins read

Stop ignoring the DOM: It’s the only thing that actually matters

Actually, I should clarify — I watched a junior developer try to install a 450KB npm package last Tuesday just to toggle a CSS class. I wish I was joking.

We’re in 2026. We have React 19, Svelte 6, and frameworks that abstract away reality so effectively that half the industry has forgotten what a div actually is. But here’s the thing: when your fancy framework blows up, or when you need to squeeze 60fps out of a low-end Android device, the DOM (Document Object Model) is where you live or die.

I’ve been refactoring a legacy dashboard recently—stripping out a bloated jQuery mess and replacing it with vanilla JavaScript. The performance gains? Massive. We dropped memory usage by about 30% on our test devices running Chrome 142. It wasn’t magic. It was just understanding how the browser actually paints pixels.

Selecting things without losing your mind

Back in the day, we had to write verbose garbage to find elements. Now? It’s trivial. Yet I still see codebases littered with getElementById mixed with getElementsByClassName. It’s inconsistent and annoying.

Just use querySelector and querySelectorAll. They’ve been optimized to hell and back. They use CSS selector syntax, which you already know. If you can style it, you can select it.

JavaScript code on computer screen - Viewing complex javascript code on computer screen | Premium Photo
JavaScript code on computer screen – Viewing complex javascript code on computer screen | Premium Photo
// The old, sad way
const btn = document.getElementById('submit-btn');
const items = document.getElementsByClassName('list-item'); // Returns an HTMLCollection, gross

// The way that keeps you sane
const submitBtn = document.querySelector('#submit-btn');
const allItems = document.querySelectorAll('.list-item'); // Returns a NodeList

// Bonus: NodeLists are iterable in modern JS (since way back in 2016, actually)
allItems.forEach(item => {
    console.log(item.textContent); // "Clean and simple"
});

One gotcha I ran into last month: querySelectorAll returns a static NodeList. If you add elements to the DOM after you ran this query, the variable allItems won’t know about them. getElementsByClassName returns a “live” collection that updates automatically. I almost never want that behavior—it causes weird bugs where loops run longer than expected—but it’s worth knowing the difference so you don’t spend three hours debugging a ghost.

Events: Stop attaching listeners to everything

If you have a list of 500 tasks in a task manager app, and you attach a click listener to every single delete button, you are actively hurting your browser. I mean it. Each listener takes up memory. The browser has to track them all.

Event delegation is the answer. It’s one of those “interview questions” that actually matters in production. You attach one listener to the parent container and catch the event as it bubbles up.

const taskList = document.querySelector('#task-list');

// ONE listener for the whole list
taskList.addEventListener('click', (e) => {
    // Check if the thing clicked was our delete button
    // .closest() is a lifesaver for handling nested elements inside the button (like icons)
    const deleteBtn = e.target.closest('.delete-btn');
    
    if (deleteBtn) {
        const taskItem = deleteBtn.closest('.task-item');
        removeTask(taskItem.dataset.id);
        taskItem.remove(); // Removes it from the DOM immediately
    }
});

function removeTask(id) {
    console.log(Deleting task ID: ${id} from the backend...);
    // fetch call would go here
}

I used this pattern on a crypto ticker widget last week. We had thousands of row updates per minute. Attaching/detaching listeners on every row update caused a memory leak that crashed the tab after four hours. Switching to delegation fixed it instantly.

Async JS and the DOM: A messy relationship

Fetching data is easy. Getting it onto the screen without locking up the UI is the hard part. The biggest mistake I see? Touching the DOM inside a loop.

JavaScript code on computer screen - Black and white code background javascript code on computer screen ...
JavaScript code on computer screen – Black and white code background javascript code on computer screen …

Every time you append an element, the browser has to recalculate layout and repaint. Do that 50 times in a row, and your user sees a stutter. Use a DocumentFragment. It’s an off-screen DOM node. You pack everything into it, then append the whole fragment to the document in one shot. One reflow. Smooth as butter.

Here’s a practical example of fetching weather data and rendering it efficiently:

async function loadWeatherData() {
    const container = document.querySelector('#weather-dashboard');
    
    try {
        // Show loading state
        container.innerHTML = '<div class="loader">Loading forecast...</div>';
        
        // Simulating an API call
        const response = await fetch('https://api.weather-example.com/v1/forecast');
        
        if (!response.ok) throw new Error(HTTP Error: ${response.status});
        
        const data = await response.json();
        
        // Create the fragment - this is the secret sauce
        const fragment = document.createDocumentFragment();
        
        data.daily.forEach(day => {
            const card = document.createElement('div');
            card.className = 'weather-card';
            
            // Using template literals for content is fine for trusted data
            // But be careful with innerHTML if user input is involved (XSS is real)
            card.innerHTML = 
                &lt;h3>${new Date(day.date).toLocaleDateString()}&lt;/h3>
                &lt;p class="temp">${Math.round(day.temp)}°C&lt;/p>
                &lt;p class="condition">${day.condition}&lt;/p>
            ;
            
            fragment.appendChild(card);
        });
        
        // Clear loader and append everything at once
        container.innerHTML = '';
        container.appendChild(fragment);
        
    } catch (error) {
        console.error("Failed to load weather:", error);
        container.innerHTML = &lt;p class="error">Failed to load data. Try again later.&lt;/p>;
    }
}

I ran a benchmark on this exact pattern versus a naive “append-in-loop” approach. With 100 items, it doesn’t matter. With 5,000 items (like a large data grid), the DocumentFragment approach was literally 40x faster on my M3 MacBook. On a budget phone? The difference between the app freezing and actually working.

Why bother learning this in 2026?

web developer workspace - Front-End Developer Workspace in net Magazine | IT Director ...
web developer workspace – Front-End Developer Workspace in net Magazine | IT Director …

You might be thinking, “React handles the reconciliation for me.” And you’re right. Until it doesn’t.

Sometimes you need to integrate a third-party library that isn’t React-friendly. Sometimes you need to write a tiny script for a marketing landing page where a 200KB bundle is unacceptable. Or maybe you’re debugging why your useEffect is firing twice and causing a flicker. Knowing that useEffect is just an abstraction over mounting and updating DOM nodes makes the solution obvious.

The DOM isn’t scary. It’s just verbose. But it’s the metal we build on. Don’t be the developer who can’t build a simple To-Do list without spinning up a build pipeline.

Leave a Reply

Your email address will not be published. Required fields are marked *