frontend
Custom DOM Implementation in JavaScript
January 24, 2026
Custom DOM Implementation in JavaScript
Overview
Creating a custom DOM implementation demonstrates how the browser's DOM works internally. This involves building a tree structure with nodes that can have children, attributes, and text content, along with methods to create, append, and render elements.
Basic Implementation
/** Implement your own DOM */
const INDENT_SIZE = 4;
class Node {
constructor(name) {
this.name = name;
this.innerHTML = "";
this.children = [];
}
appendChild(node) {
this.children.push(node);
}
}
const getSpaces = (length) => {
return new Array(length).fill(" ").join("");
};
class VDocument extends Node {
constructor() {
super("html");
}
createElement(nodeName) {
return new Node(nodeName);
}
render() {
function printTree(currentNode, currentLevel) {
const spaces = getSpaces(currentLevel * INDENT_SIZE);
let output = "";
// Opening tag
output += `${spaces}<${currentNode.name}>\n`;
// innerHTML handling
if (currentNode.innerHTML) {
output += `${spaces}${getSpaces(INDENT_SIZE)}${currentNode.innerHTML}\n`;
}
// Loop for children
for (let i = 0; i < currentNode.children.length; i++) {
output += printTree(currentNode.children[i], currentLevel + 1);
}
// Closing tag
output += `${spaces}</${currentNode.name}>\n`;
return output;
}
console.log(printTree(this, 0));
}
}
// Usage
const vDocument = new VDocument();
const body = vDocument.createElement("body");
const div = vDocument.createElement("div");
div.innerHTML = "Hello, I am a div!";
body.appendChild(div);
vDocument.appendChild(body);
vDocument.render();
Enhanced Implementation
With More DOM Methods
class VNode {
constructor(tagName) {
this.tagName = tagName.toLowerCase();
this.attributes = {};
this.children = [];
this.textContent = '';
this.parentNode = null;
}
setAttribute(name, value) {
this.attributes[name] = value;
}
getAttribute(name) {
return this.attributes[name];
}
appendChild(child) {
child.parentNode = this;
this.children.push(child);
return child;
}
removeChild(child) {
const index = this.children.indexOf(child);
if (index > -1) {
this.children.splice(index, 1);
child.parentNode = null;
return child;
}
return null;
}
querySelector(selector) {
// Simple selector implementation
if (selector.startsWith('.')) {
const className = selector.slice(1);
return this._findByClass(className);
} else if (selector.startsWith('#')) {
const id = selector.slice(1);
return this._findById(id);
} else {
return this._findByTag(selector);
}
}
_findByClass(className) {
if (this.getAttribute('class') === className) {
return this;
}
for (const child of this.children) {
const found = child._findByClass(className);
if (found) return found;
}
return null;
}
_findById(id) {
if (this.getAttribute('id') === id) {
return this;
}
for (const child of this.children) {
const found = child._findById(id);
if (found) return found;
}
return null;
}
_findByTag(tagName) {
if (this.tagName === tagName) {
return this;
}
for (const child of this.children) {
const found = child._findByTag(tagName);
if (found) return found;
}
return null;
}
toString(indent = 0) {
const spaces = ' '.repeat(indent);
let html = `${spaces}<${this.tagName}`;
// Attributes
for (const [name, value] of Object.entries(this.attributes)) {
html += ` ${name}="${value}"`;
}
html += '>';
// Text content
if (this.textContent) {
html += this.textContent;
}
// Children
if (this.children.length > 0) {
html += '\n';
for (const child of this.children) {
html += child.toString(indent + 2) + '\n';
}
html += spaces;
}
html += `</${this.tagName}>`;
return html;
}
}
class VDocument {
constructor() {
this.documentElement = new VNode('html');
this.head = new VNode('head');
this.body = new VNode('body');
this.documentElement.appendChild(this.head);
this.documentElement.appendChild(this.body);
}
createElement(tagName) {
return new VNode(tagName);
}
createTextNode(text) {
const node = new VNode('#text');
node.textContent = text;
return node;
}
getElementById(id) {
return this.documentElement._findById(id);
}
querySelector(selector) {
return this.documentElement.querySelector(selector);
}
}
Use Cases
1. Server-Side Rendering
const doc = new VDocument();
const div = doc.createElement('div');
div.setAttribute('class', 'container');
doc.body.appendChild(div);
2. Virtual DOM
// Create virtual DOM structure
const vDOM = new VDocument();
// Manipulate without touching real DOM
// Then render to actual DOM
Best Practices
- Match Browser API: Try to match native DOM API
- Performance: Optimize for common operations
- Memory Management: Clean up references
- Error Handling: Validate operations
- Extensibility: Allow for custom nodes