news 2026/1/10 16:13:27

10 个被严重低估的 JS 特性,直接少写 500 行代码

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
10 个被严重低估的 JS 特性,直接少写 500 行代码

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

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

示例:

用户 ID 去重
// 后端返回的重复用户ID列表constduplicateIds=[101,102,102,103,103,103];// 1行去重constuniqueIds=[...newSet(duplicateIds)];console.log(uniqueIds);// [101, 102, 103]
避免重复绑定事件
constlistenedEvents=newSet();functionsafeAddEvent(eventName,handler){if(!listenedEvents.has(eventName)){window.addEventListener(eventName,handler);listenedEvents.add(eventName);// 标记已绑定}}// 调用2次也只会绑定1次 scroll 事件safeAddEvent("scroll",()=>console.log("滚动了"));safeAddEvent("scroll",()=>console.log("滚动了"));

二、Object.entries() + Object.fromEntries():对象数组互转神器

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

示例:

对象转数组 Object.entries()

Object.entries(obj) 会将对象的可枚举键值对转换为一个二维数组,格式为 [[key1, value1], [key2, value2], …]。

// 原始对象constobj={name:'张三',age:20,gender:'男'};// 对象转数组:Object.entries()constarr=Object.entries(obj);console.log(arr);// 输出:[ ['name', '张三'], ['age', 20], ['gender', '男'] ]

特点

  • 只处理对象自身的可枚举属性(不包含继承的属性,如 toString)。
  • 数组元素是 [键, 值] 的结构,键的类型为字符串(即使原对象键是数字,也会转为字符串)。
数组转对象

Object.fromEntries(arr) 会将一个二维数组(格式为 [[key1, value1], [key2, value2], …])转换为对应的对象,数组中的每个子数组的第一个元素作为对象的键,第二个元素作为值。

// 原始数组(格式必须是 [[key, value], ...])constarr=[['name','李四'],['age',25],['gender','女']];// 数组转对象:Object.fromEntries()constobj=Object.fromEntries(arr);console.log(obj);// 输出:{ name: '李四', age: 25, gender: '女' }

注意

  • 数组的子数组必须包含至少两个元素(否则键对应的值为 undefined)。
  • 若数组中存在重复的键,后面的键值对会覆盖前面的(对象键具有唯一性)。
示例 2:修改对象的键或值
constobj={name:'张三',age:20};// 1. 转数组后,修改键名和值constmodifiedArr=Object.entries(obj).map(([key,value])=>{if(key==='name')return['username',value];// 改键名if(key==='age')return[key,value+1];//改值return[key,value];});// modifiedArr: [ ['username', '张三'], ['age', 21] ]// 2. 转回对象constnewObj=Object.fromEntries(modifiedArr);console.log(newObj);// { username: '张三', age: 21 }
筛选对象属性,过滤掉空值
// 后端返回的用户信息,包含空值字段constuserInfo={name:"张三",age:28,avatar:"",// 空值,需要过滤phone:"13800138000",};// 1. 转成[key,value]数组,过滤空值;2. 转回对象constfilteredUser=Object.fromEntries(Object.entries(userInfo).filter(([key,value])=>value!==""));console.log(filteredUser);// {name: "张三", age:28, phone: "13800138000"}
URL 参数转对象(不用再写正则了)
// 地址栏的参数:?name=张三&age=28&gender=男constsearchStr=window.location.search.slice(1);// 直接转成对象,支持中文和特殊字符constparamObj=Object.fromEntries(newURLSearchParams(searchStr));console.log(paramObj);// {name: "张三", age: "28", gender: "男"}

三、?? 与 ??=:比 || 靠谱

用 || 设置默认值时,会把 0、“”、false 这些 “有效假值” 当成空值。而 ?? 只判断 null/undefined。

示例:

处理用户输入的 “有效假值”
// 用户输入的数量( 0 是有效数值,不能替换)constuserInputCount=0;// 错误:会把 0 当成空值,返回 10constwrongCount=userInputCount||10;// 正确写法:只判断 null/undefined,返回 0constcorrectCount=userInputCount??10;console.log(wrongCount,correctCount);// 10, 0
给对象补默认值(不会覆盖已有值)
// 前端传入的配置,可能缺少 retries 字段constrequestConfig={timeout:5000};// 只有当 retries 为 null/undefined 时,才赋值 3(不覆盖已有值)requestConfig.retries??=3;console.log(requestConfig);// {timeout:5000, retries:3}// 如果已有值,不会被覆盖constoldConfig={timeout:3000,retries:2};oldConfig.retries??=3;console.log(oldConfig);// {timeout:3000, retries:2}

四、Intl API:原生国际化 API

很多人会用 moment.js 处理日期、货币格式化,但这个库体积特别大(压缩后也有几十 KB);而 Intl 是浏览器原生 API,支持货币、日期、数字的本地化,体积为 0,还能自动适配地区。

示例:

多语言货币格式化(适配中英文)
constprice=1234.56;// 人民币格式(自动加 ¥ 和千分位)constcnyPrice=newIntl.NumberFormat("zh-CN",{style:"currency",currency:"CNY",}).format(price);// 美元格式(自动加 $ 和千分位)constusdPrice=newIntl.NumberFormat("en-US",{style:"currency",currency:"USD",}).format(price);console.log(cnyPrice,usdPrice);// ¥1,234.56 $1,234.56
日期本地化(不用手动拼接年月日)
constnow=newDate();// 中文日期:2025年11月3日 15:40:22constcnDate=newIntl.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 PMconstenDate=newIntl.DateTimeFormat("en-US",{year:"numeric",month:"long",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit",}).format(now);console.log(cnDate,enDate);

五、Intersection Observer:图片懒加载 + 滚动加载,不卡主线程

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

示例:

图片懒加载(可用于优化首屏加载速度)```html
``` ```javascript // 初始化观察者 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); }); ```
列表滚动加载更多(避免一次性加载过多数据)
<ulid="news-list"></ul><!-- 加载提示,放在列表底部 --><divid="load-more">加载中...</div>
constloadObserver=newIntersectionObserver((entries)=>{if(entries[0].isIntersecting){// 当加载提示进入视口,请求下一页数据fetchNextPageData().then((data)=>{renderNews(data);// 渲染新列表项});}});// 监听加载提示元素loadObserver.observe(document.getElementById("load-more"));

六、Promise.allSettled ():批量请求不 “挂掉”,比 Promise.all 更实用

如果使用 Promise.all,当批量请求时,只要有一个请求失败,Promise.all 就会直接 reject,其他成功的请求结果就拿不到了;而 allSettled 会等待所有请求完成,不管成功失败,还能分别处理结果。

示例:

批量获取用户信息 + 订单 + 消息(部分接口失败不影响整体)
// 3个并行请求,可能有失败的constrequestList=[fetch("/api/user/101"),// 成功fetch("/api/orders/101"),// 失败(比如订单不存在)fetch("/api/messages/101"),// 成功];// 等待所有请求完成,处理成功和的结果Promise.allSettled(requestList).then((results)=>{// 处理成功的请求constsuccessData=results.filter((res)=>res.status==="fulfilled").map((res)=>res.value.json());// 记录失败的请求(方便排查问题)constfailedRequests=results.filter((res)=>res.status==="rejected").map((res)=>res.reason.url);console.log("成功数据:",successData);console.log("失败接口:",failedRequests);// ["/api/orders/101"]});

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

传统如果想找某个元素的父元素,比如点击列表项找列表,需要使用 element.parentNode.parentNode,但一旦 DOM 结构变了,代码就崩了;closest() 会直接根据 CSS 选择器找最近的祖先元素,不管嵌套多少层。

示例:

点击列表项,给列表容器加高亮
<ulclass="user-list"><liclass="user-item">张三</li><liclass="user-item">李四</li></ul>
document.querySelectorAll(".user-item").forEach((item)=>{item.addEventListener("click",(e)=>{// 找到最近的.user-list(不管中间嵌套多少层)constlist=e.target.closest(".user-list");list.classList.toggle("active");// 切换高亮});});
输入框聚焦,给表单组加样式
<divclass="form-group"><label>用户名</label><inputtype="text"id="username"/></div>
constusernameInput=document.getElementById("username");usernameInput.addEventListener("focus",(e)=>{// 找到最近的.form-group,加focused样式constformGroup=e.target.closest(".form-group");formGroup.classList.add("focused");});

八、URL + URLSearchParams:处理 URL 方便多了

传统解析 URL 参数、修改参数,还要写复杂的正则表达式,有时还得处理中文编码问题;URL API 可以直接解析 URL 结构,URLSearchParams 可用于处理参数,支持增删改查,自动编码,方便多了。

示例:

解析 URL 参数(支持中文和特殊字符)
// 当前页面URL:https://xxx.com/user?name=张三&age=28&gender=男constcurrentUrl=newURL(window.location.href);// 获取参数console.log(currentUrl.searchParams.get("name"));// 张三console.log(currentUrl.hostname);// xxx.com(域名)console.log(currentUrl.pathname);// /user(路径)
修改 URL 参数,跳转新页面
consturl=newURL("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=3window.location.href=url.href;// 跳转到第3页

九、for…of 循环:比 forEach 灵活,还支持 break 和 continue

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

示例:

遍历数组,找到目标值后中断
constproductList=[{id:1,name:"手机",price:5999},{id:2,name:"电脑",price:9999},{id:3,name:"平板",price:3999},];// 找价格大于8000的产品,找到后中断for(constproductofproductList){if(product.price>8000){console.log("找到高价产品:",product);// {id:2, name:"电脑", ...}break;// 中断循环,不用遍历剩下的}}
遍历 Set,获取索引
constuniqueTags=newSet(["前端","JS","CSS"]);// 用 entries() 获取索引和值for(const[index,tag]of[...uniqueTags].entries()){console.log(`索引${index}${tag}`);// 索引 0:前端,索引 1:JS...}

十、顶层 await:模块异步初始化

以前在 ES 模块里想异步加载配置,必须写个 async 函数再调用;现在 top-level await 允许你在模块顶层直接用 await,其他模块导入时会自动等待,不用再手动处理异步。

示例:

模块初始化时加载配置
// config.js// 顶层直接 await,加载后端配置constresponse=awaitfetch("/api/config");exportconstappConfig=awaitresponse.json();// {baseUrl: "https://xxx.com", timeout: 5000}
// api.js(导入 config.js,自动等待配置加载完成)import{appConfig}from"./config.js";// 直接用配置,不用关心异步exportconstapiClient={baseUrl:appConfig.baseUrl,get(url){returnfetch(`${this.baseUrl}${url}`,{timeout:appConfig.timeout});},};
点击按钮动态加载组件(按需加载,减少首屏体积)
// 点击“图表”按钮,才加载图表组件document.getElementById("show-chart-btn").addEventListener("click",async()=>{// 动态导入图表模块,await 等待加载完成const{renderChart}=awaitimport("./chart-module.js");renderChart("#chart-container");// 渲染图表});

结语

可以看到,以前我们依赖的第三方库,其实原生 API 早就能解决,比如用 Intl 替代 moment.js,用 Set 替代 lodash 的 uniq,用 Intersection Observer 替代懒加载,随着老旧的浏览器被 内容过长

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/1/9 11:41:19

5大核心参数精准调优:从理论到实践的Faiss HNSW索引优化指南

5大核心参数精准调优&#xff1a;从理论到实践的Faiss HNSW索引优化指南 【免费下载链接】faiss A library for efficient similarity search and clustering of dense vectors. 项目地址: https://gitcode.com/GitHub_Trending/fa/faiss 面对海量向量数据的检索挑战&am…

作者头像 李华
网站建设 2025/12/28 3:48:55

LeetCode 最小覆盖子串:滑动窗口 + 哈希表高效解法

引言&#xff1a;为什么这道题是算法面试高频题&#xff1f;“最小覆盖子串”&#xff08;LeetCode 76&#xff09;是字符串处理领域的经典难题&#xff0c;也是大厂面试中高频出现的算法题。它的核心考点是滑动窗口&#xff08;双指针&#xff09; 与哈希表的结合运用&#xf…

作者头像 李华
网站建设 2026/1/8 11:22:19

BuildKit配置文件全方位调优:从入门到精通实战手册

BuildKit配置文件全方位调优&#xff1a;从入门到精通实战手册 【免费下载链接】buildkit concurrent, cache-efficient, and Dockerfile-agnostic builder toolkit 项目地址: https://gitcode.com/GitHub_Trending/bu/buildkit 在容器化开发日益普及的今天&#xff0c;…

作者头像 李华
网站建设 2026/1/5 17:09:45

Netcode for GameObjects Boss Room 多人RPG战斗(19)

ActionPlayers ActionPlayers是Boss Room项目中负责管理和执行动作(Action)的核心组件,分为客户端和服务器端两个版本,分别处理动作的视觉表现和逻辑执行。 1. 系统架构 1.1 核心组件 组件 职责 位置 ClientActionPlayer 客户端动作可视化与生命周期管理 Assets/Scripts/G…

作者头像 李华
网站建设 2026/1/4 5:29:08

深度学习优化器算法巧思速览

1. 为什么要研究优化器算法&#xff1f;它的关联问题&#xff1a;训练为什么要调参&#xff0c;调的是什么参&#xff1f;如果就这个问题去问各种大语言模型&#xff0c;它们能给出一堆的理由。但就博主而言&#xff0c;答案只有一个&#xff1a;干掉调参&#xff0c;解放生产力…

作者头像 李华