12 Underused JavaScript Features You Should Start Using Today


As frontend developers, we're always looking for ways to write cleaner, more efficient code. JavaScript is constantly evolving, adding new capabilities that can make our lives easier, yet many powerful features remain surprisingly underused.
In this article, I'll walk you through 12 JavaScript features that even experienced developers often overlook. These aren't just academic curiosities, they're practical tools that can help you build better applications today.
1. AbortController
The AbortController is one of those "why didn't I know about this sooner?" features. It lets you cancel asynchronous operations like fetch requests, something that was surprisingly difficult before.
What it does:
Creates a signal that can be used to abort one or more web requests.
Real-world use case:
Canceling API calls when a user navigates away or quickly types in a search field (preventing race conditions).
// Create a controller
const controller = new AbortController();
const signal = controller.signal;
// Use the signal with fetch
fetch('/api/search?q=javascript', { signal })
.then(response => response.json())
.then(data => displayResults(data))
.catch(error => {
if (error.name === 'AbortError') {
console.log('Search canceled');
} else {
console.error('Error:', error);
}
});
// Later, if needed:
controller.abort(); // Cancels the request
This is incredibly useful for search features, preventing unnecessary network traffic, and improving user experience by avoiding stale results.
2. queueMicrotask()
This little-known function gives you precise control over the JavaScript event loop.
What it does:
Schedules a function to run immediately after the current script, but before any rendering or I/O.
Real-world use case:
When you need something to happen asynchronously, but sooner than setTimeout(fn, 0).
function processDataChunk(items) {
// Process first item
processItem(items[0]);
if (items.length > 1) {
// Schedule the rest for the next microtask
queueMicrotask(() => {
processDataChunk(items.slice(1));
});
}
}
// This keeps the UI responsive while processing large datasets
processDataChunk(largeDataset);
Unlike setTimeout (which uses macrotasks), queueMicrotask runs before the browser does any rendering, making it perfect for tasks that need to finish quickly without blocking the main thread.
3. DocumentFragment
If you're manipulating the DOM a lot, DocumentFragment can dramatically improve performance.
What it does:
Creates a lightweight container for DOM nodes that isn't part of the active document tree.
Real-world use case:
Adding multiple elements to the DOM at once, reducing reflows/repaints.
// Instead of this (causes multiple reflows)
function addItems(count) {
const list = document.getElementById('myList');
for (let i = 0; i < count; i++) {
const item = document.createElement('li');
item.textContent = `Item ${i}`;
list.appendChild(item); // Each append causes a reflow
}
}
// Do this (only one reflow)
function addItemsOptimized(count) {
const fragment = document.createDocumentFragment();
for (let i = 0; i < count; i++) {
const item = document.createElement('li');
item.textContent = `Item ${i}`;
fragment.appendChild(item);
}
document.getElementById('myList').appendChild(fragment); // Single reflow
}
Using DocumentFragment can make your DOM operations dramatically faster, especially when working with large numbers of elements.
4. Intl.DateTimeFormat
Stop wrestling with date formatting or using heavy libraries. JavaScript has powerful internationalization built in.
What it does:
Formats dates based on language and locale conventions.
Real-world use case:
Displaying dates in a user's preferred format without third-party libraries.
// Using different locales
const date = new Date();
const usFormatter = new Intl.DateTimeFormat('en-US');
console.log(usFormatter.format(date)); // 5/1/2025
const germanFormatter = new Intl.DateTimeFormat('de-DE');
console.log(germanFormatter.format(date)); // 1.5.2025
// With custom options
const options = {
weekday: 'long',
year: 'numeric',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
};
const formatter = new Intl.DateTimeFormat('en-US', options);
console.log(formatter.format(date)); // Thursday, May 1, 2025, 02:30 PM
The Intl API also includes NumberFormat, RelativeTimeFormat, and other useful formatters, making many date and number formatting libraries unnecessary.
5. WeakMap
WeakMap is perfect for associating data with objects without causing memory leaks.
What it does:
Creates a collection of key-value pairs where keys must be objects and are weakly referenced.
Real-world use case:
Storing private data for objects or DOM elements.
// Using WeakMap for private data
const privateData = new WeakMap();
class User {
constructor(name, age) {
// Store sensitive data privately
privateData.set(this, {
name,
age,
loginHistory: []
});
}
getName() {
return privateData.get(this).name;
}
login() {
const data = privateData.get(this);
data.loginHistory.push(new Date());
}
}
const user = new User('Alice', 29);
console.log(user.getName()); // "Alice"
// When user is garbage collected, the private data is too
Unlike regular Maps, WeakMaps allow the keys to be garbage collected if there are no other references to them, making WeakMap ideal for caches or storing metadata about objects.
6. requestIdleCallback()
This feature helps you work with the browser's rendering cycle to optimize performance.
What it does:
Schedules a function to be called during the browser's idle periods.
Real-world use case:
Running non-critical tasks without impacting user experience.
// Non-urgent work deferred to idle time
function processInBackground(data) {
requestIdleCallback(deadline => {
// Check how much time we have
while (deadline.timeRemaining() > 0 && data.length > 0) {
const item = data.pop();
processItem(item);
}
// If there's more work, continue in the next idle period
if (data.length > 0) {
processInBackground(data);
}
});
}
// Great for analytics, prefetching, or cleanup tasks
processInBackground(analyticsData);
This is extremely useful for improving perceived performance by deferring non-urgent tasks until the browser isn't busy with more important work.
7. Object.fromEntries()
This is the counterpart to Object.entries()
and it's surprisingly useful.
What it does:
Transforms a list of key-value pairs into an object.
Real-world use case:
Converting Map objects, form data, or URL parameters into plain objects.
// Convert form data to object
const form = document.querySelector('form');
const formData = new FormData(form);
const formObject = Object.fromEntries(formData);
console.log(formObject); // { username: 'john', email: 'john@example.com' }
// Convert URL params to object
const params = new URLSearchParams(window.location.search);
const paramsObject = Object.fromEntries(params);
// Transform data with Map and back to object
const map = new Map(Object.entries({ name: 'Alice', age: 25 }));
map.set('role', 'admin'); // Add new property
const newObject = Object.fromEntries(map);
console.log(newObject); // { name: 'Alice', age: 25, role: 'admin' }
This method makes it much easier to work with different collection types and convert between them.
8. Performance.mark() / Performance.measure()
Built-in performance measurement tools can help you identify bottlenecks.
What it does:
Lets you create custom performance measurements within your code.
Real-world use case:
Measuring how long critical operations take in production.
// Start timing
performance.mark('renderStart');
// Do expensive operation
renderComplexUI();
// End timing
performance.mark('renderEnd');
// Create a named measurement
performance.measure('renderTime', 'renderStart', 'renderEnd');
// Get results
const measurements = performance.getEntriesByName('renderTime');
console.log(`Rendering took ${measurements[0].duration.toFixed(2)} ms`);
// Clear marks if needed
performance.clearMarks();
These APIs interact with the browser's DevTools, giving you accurate timing information without disrupting your code.
9. URL and URLSearchParams
Stop manipulating URLs with string operations and regular expressions!
What it does:
Provides methods for working with URLs and their query parameters.
Real-world use case:
Building, parsing, and manipulating URLs for API calls and navigation.
// Creating a URL
const url = new URL('https://api.example.com/search');
url.searchParams.append('q', 'javascript');
url.searchParams.append('sort', 'recent');
console.log(url.toString()); // https://api.example.com/search?q=javascript&sort=recent
// Parsing an existing URL
const currentUrl = new URL(window.location.href);
const productId = currentUrl.searchParams.get('id');
// Updating query parameters
currentUrl.searchParams.set('page', '2');
history.pushState({}, '', currentUrl);
// Parsing query string directly
const params = new URLSearchParams('category=books&author=tolkien');
params.append('format', 'hardcover');
console.log(params.toString()); // category=books&author=tolkien&format=hardcover
These APIs handle all the encoding/decoding and make working with URLs much safer and more reliable.
10. requestAnimationFrame()
For smooth animations, this is far better than setInterval or setTimeout.
What it does:
Schedules a function to run before the next repaint, synchronizing with the browser's refresh rate.
Real-world use case:
Creating smooth animations or visualizations.
let position = 0;
const box = document.querySelector('.animated-box');
function animate() {
position += 2;
box.style.transform = `translateX(${position}px)`;
// Continue animation if not done
if (position < 300) {
requestAnimationFrame(animate);
}
}
// Start animation
requestAnimationFrame(animate);
Unlike interval-based animations, requestAnimationFrame automatically adjusts to the screen's refresh rate and pauses when the tab isn't visible, saving battery and CPU usage.
11. Map
Map is far more powerful than regular objects for key-value storage.
What it does:
Creates collections of key-value pairs with any data type as keys, maintaining insertion order.
Real-world use case:
Creating lookup tables, caches, or data structures with non-string keys.
// Using objects as keys
const userScores = new Map();
const user1 = { id: 1, name: 'Alice' };
const user2 = { id: 2, name: 'Bob' };
userScores.set(user1, 85);
userScores.set(user2, 92);
console.log(userScores.get(user1)); // 85
// Iteration maintains insertion order
userScores.forEach((score, user) => {
console.log(`${user.name}: ${score}`);
});
// Size property and built-in methods
console.log(userScores.size); // 2
console.log(userScores.has(user1)); // true
// Convert to array
const entries = [...userScores.entries()];
Maps have better performance for frequent additions and removals, support any type as keys, maintain order, and have useful built-in methods that objects lack.
12. Object.defineProperty()
This gives you fine-grained control over object properties.
What it does:
Defines a new property on an object with specific behaviors like read-only, enumerable, or computed values.
Real-world use case:
Creating immutable properties or custom getters/setters.
const product = {};
// Define a read-only property
Object.defineProperty(product, 'id', {
value: 'xyz123',
writable: false,
enumerable: true
});
// Define a computed property
Object.defineProperty(product, 'price', {
enumerable: true,
get() {
return this._price;
},
set(value) {
if (value < 0) throw new Error('Price cannot be negative');
this._price = value;
}
});
product.price = 99.99;
product.id = 'abc'; // Silently fails (or throws in strict mode)
console.log(product.price); // 99.99
console.log(product.id); // "xyz123"
// Hidden property (not enumerable)
Object.defineProperty(product, 'secretDiscount', {
value: 0.2,
enumerable: false
});
// Won't appear in for...in or Object.keys()
console.log(Object.keys(product)); // ["id", "price"]
This method gives you the power to create more robust objects with validation, immutability, and controlled access patterns.
Honorable Mentions
These didn't make the top 12, but are still worth knowing:
structuredClone(): Finally, a native way to deep clone objects without JSON hacks.
Intl.RelativeTimeFormat: Format "time ago" strings (e.g., "2 days ago") with localization.
Set: For creating collections of unique values.
Passive Event Listeners: Huge scroll performance boost with
{ passive: true }
.Array.prototype.at(): Access array elements from the end using negative indices.
Conclusion
Modern JavaScript is packed with powerful features that can make your code cleaner, faster, and more maintainable. By incorporating these underused functions and classes into your toolkit, you'll be able to solve common problems more elegantly and with fewer external dependencies.
The best part? All of these features are available in modern browsers today, no need to wait for the future. Start experimenting with them in your next project, and you might be surprised at how much they improve your development experience.
What's your favorite underused JavaScript feature? Let me know in the comments below!
Subscribe to my newsletter
Read articles from GASTON CHE directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
