DOM & Browser APIs Skill
Quick Reference Card
Element Selection
// Modern (preferred)
document.querySelector('#id');
document.querySelector('.class');
document.querySelectorAll('div.item');
// Scoped
container.querySelector('.child');
// Cache references
const els = {
form: document.querySelector('#form'),
input: document.querySelector('#input'),
btn: document.querySelector('#btn')
};
DOM Manipulation
// Safe text (XSS-safe)
el.textContent = userInput;
// Create elements (safest)
const div = document.createElement('div');
div.className = 'card';
div.textContent = title;
parent.appendChild(div);
// Batch updates (performance)
const fragment = document.createDocumentFragment();
items.forEach(item => {
const li = document.createElement('li');
li.textContent = item;
fragment.appendChild(li);
});
list.appendChild(fragment);
Event Handling
// Event delegation (best practice)
list.addEventListener('click', (e) => {
if (e.target.matches('.delete')) {
e.target.closest('li').remove();
}
});
// Options
el.addEventListener('click', handler, {
once: true, // Remove after first call
passive: true, // Never preventDefault
capture: false // Bubbling phase
});
// Cleanup with AbortController
const controller = new AbortController();
el.addEventListener('click', handler, { signal: controller.signal });
controller.abort(); // Remove listener
Storage APIs
// LocalStorage (persistent)
localStorage.setItem('key', JSON.stringify(data));
const data = JSON.parse(localStorage.getItem('key'));
// SessionStorage (tab-only)
sessionStorage.setItem('token', value);
// Safe wrapper
const storage = {
get: (k, def = null) => {
try { return JSON.parse(localStorage.getItem(k)) ?? def; }
catch { return def; }
},
set: (k, v) => localStorage.setItem(k, JSON.stringify(v))
};
Fetch API
// GET
const res = await fetch('/api/data');
const data = await res.json();
// POST
await fetch('/api/data', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
// With error handling
async function fetchJSON(url) {
const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
}
Modern Web APIs
// Intersection Observer (lazy loading)
const observer = new IntersectionObserver((entries) => {
entries.forEach(e => {
if (e.isIntersecting) {
e.target.src = e.target.dataset.src;
observer.unobserve(e.target);
}
});
});
document.querySelectorAll('img[data-src]').forEach(img => {
observer.observe(img);
});
// Clipboard
await navigator.clipboard.writeText(text);
// Geolocation
navigator.geolocation.getCurrentPosition(
pos => console.log(pos.coords),
err => console.error(err)
);
Troubleshooting
Common Issues
| Problem |
Symptom |
Fix |
| Element not found |
null returned |
Check if DOM loaded |
| Event not firing |
Handler not called |
Verify selector/delegation |
| XSS vulnerability |
Script execution |
Use textContent |
| Memory leak |
Growing memory |
Remove listeners |
Debug Checklist
// 1. Verify element exists
const el = document.querySelector('#id');
console.assert(el, 'Element not found');
// 2. Check event target
el.addEventListener('click', (e) => {
console.log('target:', e.target);
console.log('currentTarget:', e.currentTarget);
});
// 3. Monitor DOM changes
new MutationObserver(console.log)
.observe(el, { childList: true, subtree: true });
Production Patterns
Form Handling
form.addEventListener('submit', async (e) => {
e.preventDefault();
const data = Object.fromEntries(new FormData(form));
try {
btn.disabled = true;
await submitData(data);
form.reset();
} catch (err) {
showError(err.message);
} finally {
btn.disabled = false;
}
});
Debounced Input
const debouncedSearch = debounce(async (query) => {
const results = await search(query);
renderResults(results);
}, 300);
input.addEventListener('input', (e) => {
debouncedSearch(e.target.value);
});
Related
- Agent 05: DOM & Browser APIs (detailed learning)
- Skill: asynchronous: Fetch and promises
- Skill: ecosystem: Framework alternatives