While browsing a developer thread recently, I came across a discussion about the most underrated JavaScript features — features that quietly save hundreds of lines of code and make your apps leaner. Here’s a curated list with fresh examples and rewritten code for 2025.
1. Set — Instant Deduplication + O(1) Lookups
Use cases: Remove duplicates, prevent event re-binding.
const idPool = [101, 102, 102, 103, 103, 103];
const dedupedIds = [...new Set(idPool)];
console.log(dedupedIds); // [101, 102, 103]Prevent duplicate event bindings:
const wiredEvents = new Set();
function bindOnce(evtName, handler) {
if (wiredEvents.has(evtName)) return;
window.addEventListener(evtName, handler);
wiredEvents.add(evtName);
}
bindOnce('scroll', () => console.log('scrolling'));
bindOnce('scroll', () => console.log('scrolling'));2. Object.entries() + Object.fromEntries() — Object Transformer Power Duo
Filter null or empty values from objects:
const rawProfile = {
fullName: 'Zhang San',
age: 28,
avatar: '',
phone: '13800138000',
};
const compactProfile = Object.fromEntries(
Object.entries(rawProfile).filter(([k, v]) => v !== '')
);
console.log(compactProfile);
// { fullName: 'Zhang San', age: 28, phone: '13800138000' }Parse URL queries without regex:
const queryString = window.location.search.slice(1);
const queryMap = Object.fromEntries(new URLSearchParams(queryString));
console.log(queryMap);3. ?? and ??= — Safer Defaults than ||
Unlike ||, nullish coalescing doesn’t override valid falsy values like 0 or ''.
const qtyInput = 0;
const wrongQty = qtyInput || 10;
const safeQty = qtyInput ?? 10;
console.log(wrongQty, safeQty); // 10, 0Add default fields safely:
const reqOpts = { timeout: 5000 };
reqOpts.retries ??= 3;
console.log(reqOpts);4. Intl — Native i18n for Currency, Numbers, Dates
Format prices easily:
const priceValue = 1234.56;
const priceCNY = new Intl.NumberFormat('zh-CN', { style: 'currency', currency: 'CNY' }).format(priceValue);
const priceUSD = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(priceValue);
console.log(priceCNY, priceUSD);Localized dates:
const clock = new Date();
const zhDate = new Intl.DateTimeFormat('zh-CN', { year: 'numeric', month: 'long', day: 'numeric' }).format(clock);
const enDate = new Intl.DateTimeFormat('en-US', { year: 'numeric', month: 'long', day: 'numeric' }).format(clock);
console.log(zhDate, enDate);5. IntersectionObserver — Lazy Images + Infinite Lists
Lazy-load images:
<img data-src="https://site.com/photo.jpg" src="placeholder.jpg" class="js-lazy" />const imageWatcher = new IntersectionObserver((batch) => {
for (const entry of batch) {
if (!entry.isIntersecting) continue;
const pic = entry.target;
pic.src = pic.dataset.src;
imageWatcher.unobserve(pic);
}
});
document.querySelectorAll('.js-lazy').forEach((img) => imageWatcher.observe(img));Infinite scrolling:
const sentinel = document.getElementById('load-flag');
const listWatcher = new IntersectionObserver(async ([entry]) => {
if (!entry.isIntersecting) return;
const newItems = await fetchNextPage();
renderList(newItems);
});
listWatcher.observe(sentinel);6. Promise.allSettled() — Batch Requests Without Failing Fast
Unlike Promise.all, allSettled() waits for every result.
const batchTasks = [
fetch('/api/user/101'),
fetch('/api/orders/101'),
fetch('/api/messages/101'),
];
const outcomes = await Promise.allSettled(batchTasks);
const okPayloads = await Promise.all(outcomes.filter(r => r.status === 'fulfilled').map(r => r.value.json()));
const badEndpoints = outcomes.filter(r => r.status === 'rejected').map(r => r.reason?.url ?? '(unknown)');
console.log('OK:', okPayloads);
console.log('Failed:', badEndpoints);7. Element.closest() — Reliable DOM Traversal
Highlight container when clicking a child:
<ul class="people-list">
<li class="people-item">Alice</li>
<li class="people-item">Bob</li>
</ul>document.querySelectorAll('.people-item').forEach((node) => {
node.addEventListener('click', (ev) => {
const wrapper = ev.target.closest('.people-list');
wrapper?.classList.toggle('is-active');
});
});8. URL + URLSearchParams — No More Regex URL Manipulation
const current = new URL(window.location.href);
console.log(current.hostname);
console.log(current.pathname);
console.log(current.searchParams.get('name'));Modify query parameters:
const link = new URL('https://example.com/page');
link.searchParams.append('page', '3');
link.searchParams.append('limit', '10');
link.searchParams.set('page', '4');
link.searchParams.delete('limit');
console.log(link.href);9. for…of — Iterable-Friendly and Breakable
Stop mid-loop when found:
const catalog = [
{ id: 1, label: 'Phone', price: 5999 },
{ id: 2, label: 'Laptop', price: 9999 },
{ id: 3, label: 'Tablet', price: 3999 },
];
for (const item of catalog) {
if (item.price > 8000) {
console.log('High-value item:', item);
break;
}
}Iterate a Set with indexes:
const tagBag = new Set(['Frontend', 'JS', 'CSS']);
for (const [idx, token] of [...tagBag].entries()) {
console.log(`Index ${idx}: ${token}`);
}10. Top-Level Await — Async Modules Without Wrappers
config.mjs
const resp = await fetch('/api/config');
export const runtimeConfig = await resp.json();api.mjs
import { runtimeConfig } from './config.mjs';
export const httpClient = {
base: runtimeConfig.baseUrl,
get(path) {
return fetch(`${this.base}${path}`);
},
};Dynamic import on user action:
document.getElementById('btn-charts').addEventListener('click', async () => {
const { mountChart } = await import('./charts.mjs');
mountChart('#chart-area');
});✅ Wrap-Up
A lot of what we used to need libraries for—uniq, moment, lazy loading—now has native APIs.
Modern JavaScript has matured: it’s fast, expressive, and powerful right out of the box.
Embrace these hidden gems, and you’ll spend more time solving real problems, not rewriting helpers.