frontend
GetElementById Polyfill in JavaScript
January 24, 2026
GetElementById Polyfill in JavaScript
Overview
A polyfill for getElementById implements the native DOM method's functionality using alternative approaches. This is useful for understanding how DOM traversal works, creating custom implementations, or supporting environments where the native method might not be available.
Basic Implementation
/** Polyfill for getElementById */
document.getCustomElementById = function (id) {
// Traverse the DOM tree starting from document.body
function traverse(node) {
if (node && node.id === id) {
return node; // Found the element with the specified ID
}
for (let i = 0; i < node.childNodes.length; i++) {
const found = traverse(node.childNodes[i]);
if (found) {
return found; // Return the found element if any
}
}
return null; // Element with the specified ID not found in this subtree
}
// Start traversal from document.body
return traverse(document.body);
};
console.log(document.getCustomElementById("content1"));
Enhanced Implementation
With Document Root
document.getCustomElementById = function(id) {
if (!id || typeof id !== "string") {
return null;
}
function traverse(node) {
// Check current node
if (node && node.id === id) {
return node;
}
// Traverse children
if (node && node.childNodes) {
for (let i = 0; i < node.childNodes.length; i++) {
const child = node.childNodes[i];
const found = traverse(child);
if (found) {
return found;
}
}
}
return null;
}
// Start from document root
return traverse(document.documentElement) || traverse(document.body);
};
Iterative Approach
document.getCustomElementById = function(id) {
if (!id || typeof id !== "string") {
return null;
}
// Use stack for iterative traversal
const stack = [document.documentElement];
while (stack.length > 0) {
const node = stack.pop();
if (node && node.id === id) {
return node;
}
// Add children to stack
if (node && node.childNodes) {
for (let i = node.childNodes.length - 1; i >= 0; i--) {
stack.push(node.childNodes[i]);
}
}
}
return null;
};
Using querySelector (Modern)
document.getCustomElementById = function(id) {
if (!id || typeof id !== "string") {
return null;
}
// Use querySelector as fallback
if (document.querySelector) {
return document.querySelector(`#${CSS.escape(id)}`);
}
// Fallback to traversal
return traverseDOM(document.documentElement, id);
};
Complete Implementation
document.getCustomElementById = function(id) {
// Validate input
if (id == null || typeof id !== "string" || id === "") {
return null;
}
// Use native method if available
if (document.getElementById) {
return document.getElementById(id);
}
// Custom implementation
function traverse(node) {
// Check if node has the target ID
if (node && node.nodeType === Node.ELEMENT_NODE && node.id === id) {
return node;
}
// Traverse children
if (node && node.childNodes) {
for (let child of node.childNodes) {
const found = traverse(child);
if (found) {
return found;
}
}
}
return null;
}
// Start from document root
return traverse(document.documentElement) || traverse(document.body);
};
Performance Optimized Version
document.getCustomElementById = function(id) {
if (!id || typeof id !== "string") {
return null;
}
// Use native if available (fastest)
if (document.getElementById) {
return document.getElementById(id);
}
// Use querySelector if available
if (document.querySelector) {
try {
return document.querySelector(`#${CSS.escape(id)}`);
} catch (e) {
// Fallback to traversal
}
}
// Custom traversal (slowest)
const stack = [document.documentElement];
const visited = new WeakSet();
while (stack.length > 0) {
const node = stack.pop();
if (visited.has(node)) continue;
visited.add(node);
if (node.nodeType === Node.ELEMENT_NODE && node.id === id) {
return node;
}
if (node.childNodes) {
for (let i = node.childNodes.length - 1; i >= 0; i--) {
const child = node.childNodes[i];
if (child.nodeType === Node.ELEMENT_NODE) {
stack.push(child);
}
}
}
}
return null;
};
Use Cases
1. Learning DOM Traversal
// Understanding how getElementById works internally
function explainGetElementById(id) {
console.log("Searching for element with ID:", id);
let steps = 0;
function traverse(node, depth = 0) {
steps++;
const indent = " ".repeat(depth);
console.log(`${indent}Checking: ${node.nodeName}${node.id ? ` (id="${node.id}")` : ""}`);
if (node.id === id) {
console.log(`${indent}✓ Found!`);
return node;
}
for (let child of node.childNodes) {
if (child.nodeType === Node.ELEMENT_NODE) {
const found = traverse(child, depth + 1);
if (found) return found;
}
}
return null;
}
const result = traverse(document.body);
console.log(`Total steps: ${steps}`);
return result;
}
2. Custom ID Resolution
document.getCustomElementById = function(id, options = {}) {
const {
caseSensitive = true,
searchIn = document.body,
stopOnFirst = true
} = options;
function traverse(node) {
let nodeId = node.id;
if (!caseSensitive) {
nodeId = nodeId?.toLowerCase();
id = id.toLowerCase();
}
if (nodeId === id) {
return node;
}
for (let child of node.childNodes) {
if (child.nodeType === Node.ELEMENT_NODE) {
const found = traverse(child);
if (found && stopOnFirst) {
return found;
}
}
}
return null;
}
return traverse(searchIn);
};
3. Multiple Elements with Same ID (Non-standard)
document.getCustomElementsById = function(id) {
const results = [];
function traverse(node) {
if (node.nodeType === Node.ELEMENT_NODE && node.id === id) {
results.push(node);
}
for (let child of node.childNodes) {
traverse(child);
}
}
traverse(document.body);
return results;
};
Comparison with Native Method
Native getElementById
// Fast, optimized by browser
const element = document.getElementById("myId");
Custom Implementation
// Slower, but customizable
const element = document.getCustomElementById("myId");
Best Practices
- Use Native When Available: Always prefer native methods
- Validate Input: Check for null/undefined/empty strings
- Handle Edge Cases: Document fragments, shadow DOM
- Performance: Use iterative approach for deep trees
- Error Handling: Handle invalid IDs gracefully
- Testing: Test with various DOM structures
Common Issues
Issue 1: Not Checking Node Type
// ❌ Wrong: Checks text nodes
function traverse(node) {
if (node.id === id) return node;
// ...
}
// ✅ Correct: Only check element nodes
function traverse(node) {
if (node.nodeType === Node.ELEMENT_NODE && node.id === id) {
return node;
}
// ...
}
Issue 2: Not Handling Document Fragment
// ✅ Handle document fragments
function getElementById(id, root = document) {
function traverse(node) {
if (node.nodeType === Node.ELEMENT_NODE && node.id === id) {
return node;
}
// Handle both regular nodes and document fragments
const children = node.childNodes || node.children;
for (let child of children) {
const found = traverse(child);
if (found) return found;
}
return null;
}
return traverse(root);
}
Real-World Example
class CustomDOM {
static getElementById(id, root = document) {
// Validate
if (!id || typeof id !== "string") {
throw new TypeError("ID must be a non-empty string");
}
// Use native if available
if (root.getElementById) {
return root.getElementById(id);
}
// Custom implementation
const stack = [root.documentElement || root];
while (stack.length > 0) {
const node = stack.pop();
if (node.nodeType === Node.ELEMENT_NODE && node.id === id) {
return node;
}
if (node.children) {
for (let i = node.children.length - 1; i >= 0; i--) {
stack.push(node.children[i]);
}
}
}
return null;
}
static getAllElementsById(id) {
const results = [];
const stack = [document.documentElement];
while (stack.length > 0) {
const node = stack.pop();
if (node.nodeType === Node.ELEMENT_NODE && node.id === id) {
results.push(node);
}
if (node.children) {
for (let child of node.children) {
stack.push(child);
}
}
}
return results;
}
}