一文分享10個被嚴重低估的JS特性
前言
最近逛 Reddit 的時候,看到一個關于最被低估的 JavaScript 特性的討論,我對此進行了總結,和大家分享一下。
1. Set:數(shù)組去重 + 快速查找,比 filter 快 3 倍
提到數(shù)組去重,很多人第一反應是 filter + indexOf,但這種寫法的時間復雜度是 O (n²),而 Set 天生支持 “唯一值”,查找速度是 O (1),還能直接轉數(shù)組。
舉個例子:
用戶 ID 去重:
// 后端返回的重復用戶 ID 列表 const duplicateIds = [101, 102, 102, 103, 103, 103]; // 1 行去重 const uniqueIds = [...new Set(duplicateIds)]; console.log(uniqueIds); // [101,102,103]
避免重復綁定事件:
const listenedEvents = new Set();
// 封裝事件綁定函數(shù),防止同一事件重復綁定
function safeAddEvent(eventName, handler) {
if (!listenedEvents.has(eventName)) {
window.addEventListener(eventName, handler);
listenedEvents.add(eventName); // 標記已綁定
}
}
// 調用 2 次也只會綁定 1 次 scroll 事件
safeAddEvent("scroll", () => console.log("滾動了"));
safeAddEvent("scroll", () => console.log("滾動了"));
2. Object.entries () + Object.fromEntries ():對象數(shù)組互轉神器
以前想遍歷對象,要用 for...in 循環(huán),外加判斷 hasOwnProperty;如果想把數(shù)組轉成對象,只能手動寫循環(huán)。這對組合直接一鍵搞定。
舉個例子:
篩選對象屬性,過濾掉空值:
// 后端返回的用戶信息,包含空值字段
const userInfo = {
name: "張三",
age: 28,
avatar: "", // 空值,需要過濾
phone: "13800138000",
};
// 1. 轉成[key,value]數(shù)組,過濾空值;2. 轉回對象
const filteredUser = Object.fromEntries(Object.entries(userInfo).filter(([key, value]) => value !== ""));
console.log(filteredUser);
// {name: "張三", age:28, phone: "13800138000"}
URL 參數(shù)轉對象(不用再寫正則了)
// 地址欄的參數(shù):?name=張三&age=28&gender=男
const searchStr = window.location.search.slice(1);
// 直接轉成對象,支持中文和特殊字符
const paramObj = Object.fromEntries(new URLSearchParams(searchStr));
console.log(paramObj); // {name: "張三", age: "28", gender: "男"}
3. ?? 與 ??=:比 || 靠譜
用 || 設置默認值時,會把 0、""、false這些 “有效假值” 當成空值。比如用戶輸入 0(表示數(shù)量),count || 10會返回 10,但這里其實應該返回 0。而??只判斷 null/undefined。
舉個例子:
處理用戶輸入的 “有效假值”:
// 用戶輸入的數(shù)量( 0 是有效數(shù)值,不能替換) const userInputCount = 0; // 錯誤寫法:會把 0 當成空值,返回 10 const wrongCount = userInputCount || 10; // 正確寫法:只判斷 null/undefined,返回 0 const correctCount = userInputCount ?? 10; console.log(wrongCount, correctCount); // 10, 0
給對象補默認值(不會覆蓋已有值):
// 前端傳入的配置,可能缺少 retries 字段
const requestConfig = { timeout: 5000 };
// 只有當 retries 為 null/undefined 時,才賦值 3(不覆蓋已有值)
requestConfig.retries ??= 3;
console.log(requestConfig); // {timeout:5000, retries:3}
// 如果已有值,不會被覆蓋
const oldConfig = { timeout: 3000, retries: 2 };
oldConfig.retries ??= 3;
console.log(oldConfig); // {timeout:3000, retries:2}
4. Intl API:原生國際化 API
很多人會用 moment.js 處理日期、貨幣格式化,但這個庫體積特別大(壓縮后也有幾十 KB);而 Intl 是瀏覽器原生 API,支持貨幣、日期、數(shù)字的本地化,體積為 0,還能自動適配地區(qū)。
舉個例子:
多語言貨幣格式化(適配中英文):
const price = 1234.56;
// 人民幣格式(自動加 ¥ 和千分位)
const cnyPrice = new Intl.NumberFormat("zh-CN", {
style: "currency",
currency: "CNY",
}).format(price);
// 美元格式(自動加 $ 和千分位)
const usdPrice = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
}).format(price);
console.log(cnyPrice, usdPrice); // ¥1,234.56 $1,234.56
日期本地化(不用手動拼接年月日):
const now = new Date();
// 中文日期:2025年11月3日 15:40:22
const cnDate = new Intl.DateTimeFormat("zh-CN", {
year: "numeric",
month: "long",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
}).format(now);
// 英文日期:November 3, 2025, 03:40:22 PM
const enDate = new Intl.DateTimeFormat("en-US", {
year: "numeric",
month: "long",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
}).format(now);
console.log(cnDate, enDate);
5. Intersection Observer:圖片懶加載 + 滾動加載,不卡主線程
傳統(tǒng)我們用 scroll事件 + getBoundingClientRect()判斷元素是否在視口,會頻繁觸發(fā)重排,導致頁面卡頓;Intersection ObserverAPI 是異步監(jiān)聽,不阻塞主線程,性能直接提升一大截。
舉個例子:
圖片懶加載(可用于優(yōu)化首屏加載速度):
<!-- 用data-src存真實圖片地址,src放占位圖 --> <img data-src="https://xxx.com/real-img.jpg" src="placeholder.jpg" class="lazy-img" />
// 初始化觀察者
const lazyObserver = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
// 當圖片進入視口
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src; // 加載真實圖片
lazyObserver.unobserve(img); // 加載后停止監(jiān)聽
}
});
});
// 給所有懶加載圖片添加監(jiān)聽
document.querySelectorAll(".lazy-img").forEach((img) => {
lazyObserver.observe(img);
});
列表滾動加載更多(避免一次性加載過多數(shù)據(jù)):
<ul id="news-list"></ul> <!-- 加載提示,放在列表底部 --> <div id="load-more">加載中...</div>
const loadObserver = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting) {
// 當加載提示進入視口,請求下一頁數(shù)據(jù)
fetchNextPageData().then((data) => {
renderNews(data); // 渲染新列表項
});
}
});
// 監(jiān)聽加載提示元素
loadObserver.observe(document.getElementById("load-more"));
6. Promise.allSettled ():批量請求不 “掛掉”,比 Promise.all 更實用
如果使用 Promise.all,當批量請求時,只要有一個請求失敗,Promise.all 就會直接 reject,其他成功的請求結果就拿不到了;而 allSettled 會等待所有請求完成,不管成功失敗,還能分別處理結果。
舉個例子:
批量獲取用戶信息 + 訂單 + 消息(部分接口失敗不影響整體):
// 3個并行請求,可能有失敗的
const requestList = [
fetch("/api/user/101"), // 成功
fetch("/api/orders/101"), // 失?。ū热缬唵尾淮嬖冢?
fetch("/api/messages/101"), // 成功
];
// 等待所有請求完成,處理成功和失敗的結果
Promise.allSettled(requestList).then((results) => {
// 處理成功的請求
const successData = results.filter((res) => res.status === "fulfilled").map((res) => res.value.json());
// 記錄失敗的請求(方便排查問題)
const failedRequests = results.filter((res) => res.status === "rejected").map((res) => res.reason.url);
console.log("成功數(shù)據(jù):", successData);
console.log("失敗接口:", failedRequests); // ["/api/orders/101"]
});
7. element.closest ():向上找父元素最安全的方式
傳統(tǒng)如果想找某個元素的父元素,比如點擊列表項找列表,需要使用 element.parentNode.parentNode,但一旦 DOM 結構變了,代碼就崩了;closest() 回直接根據(jù) CSS 選擇器找最近的祖先元素,不管嵌套多少層。
舉個例子:
點擊列表項,給列表容器加高亮:
<ul class="user-list"> <li class="user-item">張三</li> <li class="user-item">李四</li> </ul>
document.querySelectorAll(".user-item").forEach((item) => {
item.addEventListener("click", (e) => {
// 找到最近的.user-list(不管中間嵌套多少層)
const list = e.target.closest(".user-list");
list.classList.toggle("active"); // 切換高亮
});
});
輸入框聚焦,給表單組加樣式:
<div class="form-group"> <label>用戶名</label> <input type="text" id="username" /> </div>
const usernameInput = document.getElementById("username");
usernameInput.addEventListener("focus", (e) => {
// 找到最近的.form-group,加focused樣式
const formGroup = e.target.closest(".form-group");
formGroup.classList.add("focused");
});
8. URL + URLSearchParams:處理 URL 方便多了
傳統(tǒng)解析 URL 參數(shù)、修改參數(shù),還要寫復雜的正則表達式,有時還得處理中文編碼問題;當然我們會直接引入三方庫來處理,但畢竟還要引入多余的苦,其實 URL API 可以直接解析 URL 結構,URLSearchParams 可用于處理參數(shù),支持增刪改查,自動編碼,方便多了。
解析 URL 參數(shù)(支持中文和特殊字符):
// 當前頁面URL:https://xxx.com/user?name=張三&age=28&gender=男
const currentUrl = new URL(window.location.href);
// 獲取參數(shù)
console.log(currentUrl.searchParams.get("name")); // 張三
console.log(currentUrl.hostname); // xxx.com(域名)
console.log(currentUrl.pathname); // /user(路徑)
修改 URL 參數(shù),跳轉新頁面:
const url = new URL("https://xxx.com/list");
// 添加參數(shù)
url.searchParams.append("page", 2);
url.searchParams.append("size", 10);
// 修改參數(shù)
url.searchParams.set("page", 3);
// 刪除參數(shù)
url.searchParams.delete("size");
console.log(url.href); // https://xxx.com/list?page=3
window.location.href = url.href; // 跳轉到第3頁
9. for...of 循環(huán):比 forEach 靈活,還支持 break 和 continue
我們都知道,forEach 不能用 break中斷循環(huán),也不能用 continue跳過當前項。而for...of不僅支持中斷,還能遍歷數(shù)組、Set、Map、字符串,甚至獲取索引。
舉個例子:
遍歷數(shù)組,找到目標值后中斷:
const productList = [
{ id: 1, name: "手機", price: 5999 },
{ id: 2, name: "電腦", price: 9999 },
{ id: 3, name: "平板", price: 3999 },
];
// 找價格大于8000的產(chǎn)品,找到后中斷
for (const product of productList) {
if (product.price > 8000) {
console.log("找到高價產(chǎn)品:", product); // {id:2, name:"電腦", ...}
break; // 中斷循環(huán),不用遍歷剩下的
}
}
遍歷 Set,獲取索引:
const uniqueTags = new Set(["前端", "JS", "CSS"]);
// 用 entries() 獲取索引和值
for (const [index, tag] of [...uniqueTags].entries()) {
console.log(`索引${index}:${tag}`); // 索引 0:前端,索引 1:JS...
}
10. 頂層 await:模塊異步初始化
以前在 ES 模塊里想異步加載配置,必須寫個 async 函數(shù)再調用;現(xiàn)在 top-level await 允許你在模塊頂層直接用 await,其他模塊導入時會自動等待,不用再手動處理異步。
舉個例子:
模塊初始化時加載配置:
// config.js
// 頂層直接 await,加載后端配置
const response = await fetch("/api/config");
export const appConfig = await response.json(); // {baseUrl: "https://xxx.com", timeout: 5000}
// api.js(導入 config.js,自動等待配置加載完成)
import { appConfig } from "./config.js";
// 直接用配置,不用關心異步
export const apiClient = {
baseUrl: appConfig.baseUrl,
get(url) {
return fetch(`${this.baseUrl}${url}`, { timeout: appConfig.timeout });
},
};
點擊按鈕動態(tài)加載組件(按需加載,減少首屏體積):
// 點擊“圖表”按鈕,才加載圖表組件
document.getElementById("show-chart-btn").addEventListener("click", async () => {
// 動態(tài)導入圖表模塊,await 等待加載完成
const { renderChart } = await import("./chart-module.js");
renderChart("#chart-container"); // 渲染圖表
});
結語
可以看到,以前我們依賴的第三方庫,其實原生 API 早就能解決,比如用 Intl 替代 moment.js,用 Set 替代 lodash 的 uniq,用 Intersection Observer 替代懶加載,隨著老舊的瀏覽器被討論,兼容性越來越好,這些 API 以后會成為基操。
以上就是一文分享10個被嚴重低估的JS特性的詳細內容,更多關于JS特性的資料請關注腳本之家其它相關文章!
相關文章
JS獲取用戶訪問網(wǎng)頁的瀏覽器數(shù)據(jù)和IP地址信息
這篇文章主要為大家詳細介紹了如何使用JS實現(xiàn)獲取用戶訪問網(wǎng)頁的瀏覽器數(shù)據(jù)和IP地址信息,文中的示例代碼講解詳細,感興趣的小伙伴可以了解下2025-08-08
javaScript產(chǎn)生隨機數(shù)的用法小結
這篇文章主要介紹了javaScript產(chǎn)生隨機數(shù)的用法小結,包括JavaScript Math.random()內置函數(shù) ,Js 隨機數(shù)產(chǎn)生6位數(shù)字的代碼,需要的朋友可以參考下2018-04-04
Elasticsearch工具cerebro的安裝與使用教程
這篇文章主要介紹了Elasticsearch工具cerebro的安裝與使用教程,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-03-03

