javascript技巧

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript技巧 > JS特性

一文分享10个被严重低估的JS特性

作者:冴羽

最近逛 Reddit 的时候,看到一个关于最被低估的 JavaScript 特性的讨论,因此本文就对此进行了总结,和大家分享一下,希望对大家有所帮助

前言

最近逛 Reddit 的时候,看到一个关于最被低估的 JavaScript 特性的讨论,我对此进行了总结,和大家分享一下。

1. Set:数组去重 + 快速查找,比 filter 快 3 倍

提到数组去重,很多人第一反应是 filter + indexOf,但这种写法的时间复杂度是 O (n²),而 Set 天生支持 “唯一值”,查找速度是 O (1),还能直接转数组。

举个例子:

用户 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();
// 封装事件绑定函数,防止同一事件重复绑定
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 ():对象数组互转神器

以前想遍历对象,要用 for...in 循环,外加判断 hasOwnProperty;如果想把数组转成对象,只能手动写循环。这对组合直接一键搞定。

举个例子:

筛选对象属性,过滤掉空值:

// 后端返回的用户信息,包含空值字段
const userInfo = {
  name: "张三",
  age: 28,
  avatar: "", // 空值,需要过滤
  phone: "13800138000",
};
// 1. 转成[key,value]数组,过滤空值;2. 转回对象
const filteredUser = Object.fromEntries(Object.entries(userInfo).filter(([key, value]) => value !== ""));
console.log(filteredUser);
// {name: "张三", age:28, phone: "13800138000"}

URL 参数转对象(不用再写正则了)

// 地址栏的参数:?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(表示数量),count || 10会返回 10,但这里其实应该返回 0。而??只判断 null/undefined

举个例子:

处理用户输入的 “有效假值”:

// 用户输入的数量( 0 是有效数值,不能替换)
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,支持货币、日期、数字的本地化,体积为 0,还能自动适配地区。

举个例子:

多语言货币格式化(适配中英文):

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:图片懒加载 + 滚动加载,不卡主线程

传统我们用 scroll事件 + getBoundingClientRect()判断元素是否在视口,会频繁触发重排,导致页面卡顿;Intersection ObserverAPI 是异步监听,不阻塞主线程,性能直接提升一大截。

举个例子:

图片懒加载(可用于优化首屏加载速度):

<!-- 用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); // 加载后停止监听
    }
  });
});
// 给所有懒加载图片添加监听
document.querySelectorAll(".lazy-img").forEach((img) => {
  lazyObserver.observe(img);
});

列表滚动加载更多(避免一次性加载过多数据):

<ul id="news-list"></ul>
<!-- 加载提示,放在列表底部 -->
<div id="load-more">加载中...</div>
const loadObserver = new IntersectionObserver((entries) => {
  if (entries[0].isIntersecting) {
    // 当加载提示进入视口,请求下一页数据
    fetchNextPageData().then((data) => {
      renderNews(data); // 渲染新列表项
    });
  }
});
// 监听加载提示元素
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("成功数据:", successData);
  console.log("失败接口:", failedRequests); // ["/api/orders/101"]
});

7. element.closest ():向上找父元素最安全的方式

传统如果想找某个元素的父元素,比如点击列表项找列表,需要使用 element.parentNode.parentNode,但一旦 DOM 结构变了,代码就崩了;closest() 回直接根据 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 方便多了

传统解析 URL 参数、修改参数,还要写复杂的正则表达式,有时还得处理中文编码问题;当然我们会直接引入三方库来处理,但毕竟还要引入多余的苦,其实 URL API 可以直接解析 URL 结构,URLSearchParams 可用于处理参数,支持增删改查,自动编码,方便多了。

解析 URL 参数(支持中文和特殊字符):

// 当前页面URL:https://xxx.com/user?name=张三&age=28&gender=男
const currentUrl = new URL(window.location.href);

// 获取参数
console.log(currentUrl.searchParams.get("name")); // 张三
console.log(currentUrl.hostname); // xxx.com(域名)
console.log(currentUrl.pathname); // /user(路径)

修改 URL 参数,跳转新页面:

const url = new URL("https://xxx.com/list");

// 添加参数
url.searchParams.append("page", 2);
url.searchParams.append("size", 10);

// 修改参数
url.searchParams.set("page", 3);

// 删除参数
url.searchParams.delete("size");
console.log(url.href); // https://xxx.com/list?page=3
window.location.href = url.href; // 跳转到第3页

9. for...of 循环:比 forEach 灵活,还支持 break 和 continue

我们都知道,forEach 不能用 break中断循环,也不能用 continue跳过当前项。而for...of不仅支持中断,还能遍历数组、Set、Map、字符串,甚至获取索引。

举个例子:

遍历数组,找到目标值后中断:

const productList = [
  { id: 1, name: "手机", price: 5999 },
  { id: 2, name: "电脑", price: 9999 },
  { id: 3, name: "平板", price: 3999 },
];

// 找价格大于8000的产品,找到后中断
for (const product of productList) {
  if (product.price > 8000) {
    console.log("找到高价产品:", product); // {id:2, name:"电脑", ...}
    break; // 中断循环,不用遍历剩下的
  }
}

遍历 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 函数再调用;现在 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 });
  },
};

点击按钮动态加载组件(按需加载,减少首屏体积):

// 点击“图表”按钮,才加载图表组件
document.getElementById("show-chart-btn").addEventListener("click", async () => {
  // 动态导入图表模块,await 等待加载完成
  const { renderChart } = await import("./chart-module.js");
  renderChart("#chart-container"); // 渲染图表
});

结语

可以看到,以前我们依赖的第三方库,其实原生 API 早就能解决,比如用 Intl 替代 moment.js,用 Set 替代 lodash 的 uniq,用 Intersection Observer 替代懒加载,随着老旧的浏览器被讨论,兼容性越来越好,这些 API 以后会成为基操。

以上就是一文分享10个被严重低估的JS特性的详细内容,更多关于JS特性的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文