你是不是还在手写 filter+indexOf 去重?还在为 URL 参数解析烦恼? 开发新项目总在 utils 里复制粘贴?
其实,现代浏览器早已内置了更优雅的解决方案——随着 IE 彻底退出历史舞台,曾经“不敢用”的原生 API,如今已成为提升开发效率的“秘密武器”。以下 10 个技巧,每一个都能帮你少写几十行代码,同时让代码更高效、更易维护。
省流上重点:
- 🗑️
Set:数组去重+防重绑定,代替Lodash.uniq; - 🗑️
Set:数组去重+防重绑定,代替Lodash.uniq; - ↔️
Object.entries()+Object.fromEntries():对象与数组互转,告别for...in循环; - 🎯
??+??=:精准空值判断,避免||的误判; - 🌍
Intl API:原生国际化,代替moment.js; - 🔍
IntersectionObserver:高性能懒加载,代替scroll事件; - 🛡️
Promise.allSettled():批量请求容错,不用再怕“一错全灭”; - 🧭
element.closest():安全查找祖先,告别parentNode嵌套; - 🔗
URL+URLSearchParams:URL 操作现代化,告别正则拼接; - ⏱️
requestAnimationFrame:流畅动画,比setTimeout丝滑 10 倍; - ⏳
顶层await:模块异步简化,不用再写init().then()。
1. Set:新增集合 API
Set 是 ES6 引入的“唯一值集合”,天然支持快速去重和唯一性判断
痛点
传统 filter+indexOf 去重的时间复杂度是 O(n²)(每遍历一个元素都要查一遍数组),数据量大时性能极差;防重复事件绑定要维护一个数组,容易漏判或误判。
原生解法
数组去重+防重绑定的“魔法容器”
// ① 数组去重(O(n) 时间复杂度,遍历一次即可)
const uniqueArr = [...new Set([1, 2, 2, 3])] // [1, 2, 3]
// ② 防重复事件绑定(避免同一事件多次绑定)
const boundEvents = new Set() // 用Set存已绑定的事件类型
function addOnceEvent(type: string, handler: EventListener) {
if (!boundEvents.has(type)) {
window.addEventListener(type, handler)
boundEvents.add(type) // 标记为已绑定
}
}Vue3 实战示例(防重复绑定)
比如在 Vue 组件中,避免重复绑定窗口 resize 事件:
<script setup lang="ts">
import { onMounted, onUnmounted } from 'vue'
const boundEvents = new Set<string>()
const handleResize = () => {
console.log('窗口大小变化了!')
}
onMounted(() => {
addOnceEvent('resize', handleResize)
})
onUnmounted(() => {
window.removeEventListener('resize', handleResize)
boundEvents.delete('resize') // 移除标记
})
</script>优势
- 性能高:去重时间复杂度降为 O(n),查找操作 O(1)(Set 的 has 方法是哈希查找);
- 简洁:无需循环判断,一行代码搞定;
- 安全:避免事件重复绑定导致的逻辑混乱。
兼容性
- caniuse 链接 支持 Chrome 45+、Firefox 13+、Safari 8+、Edge 12+。
2. Object.entries() + Object.fromEntries():对象与数组的“双向门”
痛点
处理对象时,需要手动遍历 Object.keys 或 Object.values,再拼接成键值对,过程繁琐且易出错(比如漏写 hasOwnProperty)。
原生解法
Object.entries(obj):把对象转成[key, value]数组;Object.fromEntries(arr):把[key, value]数组转回对象。
两者结合,能轻松实现对象过滤、键名修改、URL 参数解析:
// ① 过滤对象空字段(保留非空值)
const user = { name: '张三', age: 20, address: '' }
const cleanUser = Object.fromEntries(
Object.entries(user).filter(([_, value]) => ['', null, undefined].includes(value))
) // { name: '张三', age: 20 }
// ② 解析URL参数(自动解码中文)
const url = new URL('https://example.com?name=张三&page=2')
const params = Object.fromEntries(url.searchParams) // { name: '张三', page: '2' }Vue3 实战示例(URL 参数解析)
比如在 Vue 路由守卫中,解析 URL 参数并传递给组件:
// router/index.ts
import { createRouter, createWebHistory } from 'vue-router'
import UserPage from '../views/UserPage.vue'
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/user',
component: UserPage,
beforeEnter: (to) => {
// 解析URL参数
const params = Object.fromEntries(
new URLSearchParams(to.query as string)
)
// 传递给组件
to.meta.params = params
},
},
],
})优势
- 避免循环:无需
for...in或hasOwnProperty; - 支持编码:自动处理 URL 中的中文/特殊字符(比如
张三会自动解码); - 灵活性高:可链式调用
filter/map处理对象(比如修改键名)。
兼容性
Object.entries:Chrome 54+、Firefox 47+、Safari 10.1+(caniuse 链接);Object.fromEntries:Chrome 73+、Firefox 63+、Safari 12.1+(caniuse 链接)。
3. ?? 与 ??=:精准判断空值的“智能开关”
痛点再聚焦:那些被||误判的“合法假值”
在电商购物车、库存管理等场景中,0、''往往是合法输入——比如:
- 用户输入
0表示“清空购物车中的该商品”; - 库存显示
0表示“商品售罄”; - 订单数量
0表示“取消订单”。
但||会把这些合法值误判为“假值”,比如:
// 错误示例:用户输入0,却被替换成默认值1
const quantity = userInput || 1; // userInput=0 → quantity=1这会导致“用户想清空商品,却被强制保留1件”的逻辑错误。
原生解法:用??精准区分“空值”与“合法假值”
??(空值合并运算符)仅在左侧为null或undefined时返回右侧值,完美避开0、''、false等合法假值;??=(空值赋值运算符)仅在左侧为null或undefined时赋值,相当于“若未设置,则初始化”。
针对性示例1:??解决“购物车数量误判”问题
场景:电商购物车中,用户可输入0(清空商品),未输入时默认保留1件。
<script setup lang="ts">
import { ref, computed } from 'vue'
// 用户输入的商品数量(可能为0,或未输入)
const inputQuantity = ref<number | undefined>(undefined)
// 默认数量:未输入时保留1件
const DEFAULT_QUANTITY = 1
// 🌟 核心逻辑:仅当inputQuantity为undefined/null时,用默认值
const finalQuantity = computed(() => {
// 对比:如果用||,inputQuantity=0会被替换成1,导致错误
return inputQuantity.value ?? DEFAULT_QUANTITY
})
// 提交修改:若finalQuantity=0,调用接口删除商品
const handleSubmit = async () => {
if (finalQuantity.value === 0) {
await deleteFromCart() // 删除商品
return
}
await updateCartQuantity(finalQuantity.value) // 更新数量
}
</script>
<template>
<div class="cart-item">
<img src="iphone15.jpg" alt="iPhone 15" class="item-img" />
<div class="item-info">
<h4>iPhone 15</h4>
<input
v-model.number="inputQuantity"
type="number"
min="0"
placeholder="请输入数量(0=删除)"
class="quantity-input"
/>
<button @click="handleSubmit" class="submit-btn">
{{ finalQuantity === 0 ? '删除商品' : '确认修改' }}
</button>
</div>
</div>
<p class="tip">
最终操作:{{ finalQuantity === 0 ? '删除该商品' : `保留${finalQuantity}件` }}
</p>
</template>
<style scoped>
.cart-item { display: flex; gap: 15px; padding: 10px; border-bottom: 1px solid #eee; }
.item-img { width: 80px; height: 80px; object-fit: cover; }
.quantity-input { width: 60px; padding: 5px; margin-right: 10px; }
.submit-btn { padding: 5px 10px; background: #dc3545; color: #fff; border: none; border-radius: 4px; }
.tip { color: #666; font-size: 14px; margin-top: 5px; }
</style>针对性示例2:??=解决“组件配置初始化”问题
场景:表格组件的“每页条数”配置,父组件可传递0(表示显示全部数据),未传递时默认10条。
<script setup lang="ts">
import { defineProps, ref } from 'vue'
// 父组件传递的props:pageSize可能为0(全部)或未传递
const props = defineProps<{
pageSize?: number
pageNumber?: number
}>()
// 组件内部配置:默认值仅在未设置时生效
const paginationConfig = ref<{ pageSize: number; pageNumber: number }>({
pageSize: undefined as unknown as number, // 初始化为undefined
pageNumber: undefined as unknown as number
})
// 🌟 核心逻辑:用??=为未设置的属性赋默认值
paginationConfig.value = {
// 若父组件传了pageSize(包括0),则保留原值;否则用10
pageSize: props.pageSize ??= 10,
// 若父组件传了pageNumber,保留原值;否则用1
pageNumber: props.pageNumber ??= 1
}
// 当前每页条数(可能为0=全部,或10/20等)
const currentPageSize = computed(() => paginationConfig.value.pageSize)
</script>
<template>
<div class="table-pagination">
<span>每页显示:</span>
<span class="page-size">
{{ currentPageSize === 0 ? '全部' : currentPageSize }} 条
</span>
<!-- 其他分页组件 -->
</div>
</template>为什么这两个示例更有价值?
- 场景真实:覆盖电商、组件开发等高频业务场景,直接解决实际问题;
- 对比强烈:通过注释点明
||的缺陷,突出??/??=的优势; - 逻辑清晰:用
computed和ref保持Vue3的响应式,符合现代前端开发习惯。
再补一个??=的“配置合并”场景
在接口请求配置中,我们常需要合并“默认配置”与“用户自定义配置”,且保留用户设置的0值(比如timeout=0表示“不超时”):
// 默认请求配置
const defaultConfig = {
timeout: 5000, // 默认超时5秒
headers: { 'Content-Type': 'application/json' }
}
// 用户自定义配置(timeout=0表示不超时)
const userConfig = {
timeout: 0,
headers: { 'Authorization': 'Bearer token' }
}
// 🌟 用??=合并配置:仅覆盖未设置的属性
const mergedConfig = { ...defaultConfig, ...userConfig }
mergedConfig.timeout ??= defaultConfig.timeout // 保留userConfig的0
mergedConfig.headers ??= defaultConfig.headers // 保留userConfig的headers
console.log(mergedConfig.timeout) // 0(未被替换)
console.log(mergedConfig.headers) // { Authorization: 'Bearer token' }优势
- 精准:只处理
null/undefined,避免误判; - 简洁:替代冗长的
if-else判断(比如const count = userInput !== null && userInput !== undefined ? userInput : 10); - 安全:不覆盖合法的“假值”(比如
0、'')。
下次遇到“需要兜底,但不能覆盖0/''”的场景,直接掏出??和??=——这才是现代前端处理空值的“正确姿势”!
兼容性
??:Chrome 80+、Firefox 72+、Safari 13.1+(caniuse 链接);??=:Chrome 85+、Firefox 79+、Safari 14+(caniuse 链接)。
4. Intl API:原生国际化的“全能翻译官”
痛点
moment.js 体积大(>70KB)且已停止维护;手动处理国际化(如货币、日期格式)需大量正则,容易出错。
原生解法
Intl 是浏览器内置的国际化 API(MDN 链接),支持货币格式化、日期本地化、数字千分位等功能:
// ① 货币格式化(人民币,保留两位小数)
const price = new Intl.NumberFormat('zh-CN', {
style: 'currency',
currency: 'CNY',
}).format(1234.56) // "¥1,234.56"
// ② 日期本地化(中文,完整日期+短时间)
const date = new Intl.DateTimeFormat('zh-CN', {
dateStyle: 'full',
timeStyle: 'short',
}).format(new Date()) // "2024年5月20日 星期一 14:30"
// ③ 数字千分位(中文,整数部分加逗号)
const number = new Intl.NumberFormat('zh-CN').format(1234567) // "1,234,567"Vue3 实战示例(货币格式化组件)
比如封装一个 Vue 货币格式化组件:
<script setup lang="ts">
import { computed, defineProps } from 'vue'
const props = defineProps<{
value: number | string
currency?: string // 货币类型(默认CNY)
}>()
// 计算属性:格式化货币
const formatted = computed(() => {
return new Intl.NumberFormat('zh-CN', {
style: 'currency',
currency: props.currency || 'CNY',
}).format(Number(props.value))
})
</script>
<template>
<span class="currency">{{ formatted }}</span>
</template>
<style scoped>
.currency {
color: #28a745; /* 绿色,突出金额 */
}
</style>优势
- 体积小:无需额外引入库;
- 多语言支持:覆盖 200+语言区域(比如
en-US对应美式英语,ja-JP对应日语); - 自动适配:根据用户系统设置切换语言/格式(比如用户系统是英文,日期会自动显示为
Monday, May 20, 2024 at 2:30 PM)。
兼容性
Intl.NumberFormat:Chrome 29+、Firefox 28+、Safari 10+(caniuse 链接);Intl.DateTimeFormat:Chrome 29+、Firefox 28+、Safari 10+(caniuse 链接)。
5. IntersectionObserver:高性能懒加载的“隐形观察者”
痛点
监听 scroll 事件实现懒加载,会频繁触发重排重绘(浏览器需要不断计算元素位置),导致页面卡顿;offsetTop 计算易受 DOM 结构影响,逻辑脆弱。
原生解法
IntersectionObserver 是浏览器内置的“交集观察器”(MDN 链接),能自动监听元素是否进入视口,完全不阻塞主线程:
// ① 创建观察器实例(元素进入视口时触发)
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) { // 元素可见
const img = entry.target as HTMLImageElement;
img.src = img.dataset.src || ''; // 加载真实图片
observer.unobserve(img); // 停止观察,避免重复触发
}
});
});
// ② 监听所有懒加载图片(带data-src属性)
document.querySelectorAll('img[data-src]').forEach(img => {
observer.observe(img);
});Vue3 实战示例(懒加载组件)
封装一个 Vue 懒加载图片组件:
<script setup lang="ts">
import { onMounted, ref } from 'vue'
const props = defineProps<{
src: string // 真实图片地址
alt?: string
}>()
const imgRef = ref<HTMLImageElement | null>(null)
const observer = ref<IntersectionObserver | null>(null)
onMounted(() => {
// 创建观察器
observer.value = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting && imgRef.value) {
imgRef.value.src = props.src // 加载真实图片
observer.value?.unobserve(imgRef.value) // 停止观察
}
})
})
// 监听图片元素
if (imgRef.value) {
observer.value.observe(imgRef.value)
}
})
</script>
<template>
<img
ref="imgRef"
:alt="alt"
class="lazy-img"
src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
/>
</template>
<style scoped>
.lazy-img {
width: 100%;
height: auto;
background: #f0f0f0; /* 占位背景 */
}
</style>优势
- 性能高:避免
scroll事件的频繁触发; - 无重排:浏览器层面优化,不影响渲染;
- 场景广:支持图片懒加载、元素曝光统计(比如广告曝光量)、无限滚动。
兼容性
- Chrome 51+、Firefox 55+、Safari 12.1+(caniuse 链接)。
6. Promise.allSettled():批量请求的“容错大师”
痛点
Promise.all() 任一请求失败,全部 reject——比如仪表盘需要加载用户、订单、消息三个接口,若订单接口 404,整个页面就会崩溃,无法展示部分数据。
原生解法
Promise.allSettled() 会等待所有请求完成(无论成功/失败),返回每个请求的状态(fulfilled/rejected)(MDN 链接):
const requests = [
fetch('/user'), // 成功(状态:fulfilled)
fetch('/orders'), // 404(状态:rejected)
fetch('/messages'), // 成功(状态:fulfilled)
]
// 等待所有请求完成,分别处理成功/失败
const results = await Promise.allSettled(requests)
// 提取成功的结果
const successes = results
.filter((res) => res.status === 'fulfilled')
.map((res) => res.value.json())
// 提取失败的结果(用于上报错误)
const failures = results
.filter((res) => res.status === 'rejected')
.map((res) => res.reason)Vue3 实战示例(仪表盘数据聚合)
比如在 Vue 组件中,加载多个接口并展示部分数据:
<script setup lang="ts">
import { onMounted, ref } from 'vue'
const userData = ref<any>(null)
const orderData = ref<any>(null)
const messageData = ref<any>(null)
const error = ref<string | null>(null)
onMounted(async () => {
const requests = [
fetch('/api/user'), // 用户接口
fetch('/api/orders'), // 订单接口
fetch('/api/messages'), // 消息接口
]
try {
const results = await Promise.allSettled(requests)
results.forEach((res, index) => {
if (res.status === 'fulfilled') {
// 根据接口索引分配数据
switch (index) {
case 0:
userData.value = await res.value.json()
break
case 1:
orderData.value = await res.value.json()
break
case 2:
messageData.value = await res.value.json()
break
}
} else {
// 记录错误(拼接接口URL和错误原因)
const apiUrl = requests[index].url
error.value += `接口 ${apiUrl} 失败:${res.reason.message};`
}
})
} catch (err) {
error.value = '数据加载异常,请刷新页面'
}
})
</script>
<template>
<div class="dashboard">
<h2>📊 仪表盘</h2>
<!-- 用户数据(必展示) -->
<div
v-if="userData"
class="card"
>
<h3>👥 用户数据</h3>
<p>总用户数:{{ userData.total }}</p>
<p>今日新增:{{ userData.todayNew }}</p>
</div>
<!-- 订单数据(成功才展示) -->
<div
v-if="orderData"
class="card"
>
<h3>🛒 订单数据</h3>
<p>今日订单:{{ orderData.todayOrders }}</p>
<p>客单价:{{ orderData.averagePrice }}</p>
</div>
<!-- 消息数据(成功才展示) -->
<div
v-if="messageData"
class="card"
>
<h3>💬 消息数据</h3>
<p>未读消息:{{ messageData.unread }}</p>
<p>今日新增:{{ messageData.todayNew }}</p>
</div>
<!-- 错误提示 -->
<div
v-if="error"
class="error"
>
{{ error }}
</div>
</div>
</template>7. element.closest():安全查找祖先元素的“导航仪”
痛点
用 parentNode.parentNode 查找祖先元素,DOM 结构一变就崩;或者为了找个.container,写了 5 层parentNode——代码像“俄罗斯套娃”,维护性极差 😫。
原生解法
element.closest(selector) 是浏览器原生的祖先元素查找器(MDN 链接),能自动向上遍历 DOM 树,找到最近的匹配祖先(包括元素自身)。
// 点击按钮,找到最近的.modal祖先(弹框容器)
const button = document.querySelector('.modal-btn');
button.addEventListener('click', (e) => {
const target = e.target as HTMLElement;
// 查找最近的.modal(<div class="modal">)
const modal = target.closest('.modal');
// 隐藏弹框
modal?.classList.add('hidden');
});Vue3 实战示例(弹框关闭逻辑)
在 Vue 的弹框组件中,点击“关闭”按钮时,自动找到弹框容器并隐藏:
<script setup lang="ts">
const handleClose = (e: MouseEvent) => {
const target = e.target as HTMLElement
// 查找最近的.modal容器
const modal = target.closest('.modal')
// 隐藏弹框(添加hidden类)
modal?.classList.add('hidden')
}
</script>
<template>
<div class="modal">
<div class="modal-content">
<h3>弹框标题</h3>
<p>弹框内容...</p>
<button
@click="handleClose"
class="close-btn">
关闭
</button>
</div>
</div>
</template>
<style scoped>
.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
}
.modal-content {
background: #fff;
padding: 20px;
border-radius: 8px;
width: 300px;
}
.close-btn {
margin-top: 10px;
padding: 5px 10px;
background: #dc3545;
color: #fff;
border: none;
border-radius: 4px;
cursor: pointer;
}
/* 隐藏弹框 */
.modal.hidden {
display: none;
}
</style>优势
- 鲁棒性:DOM 结构变化不影响结果(只要选择器正确);
- 简洁:替代多层
parentNode(比如target.parentNode.parentNode→target.closest('.modal')); - 灵活:支持任何 CSS 选择器(类、ID、属性选择器等)。
兼容性
- Chrome 41+、Firefox 35+、Safari 9+(caniuse 链接),覆盖 99%现代浏览器。
8. URL + URLSearchParams:URL 操作的“现代化工具箱”
痛点
手写正则解析 URL 参数,要么漏处理中文编码,要么把page=2解析成page: "2"(字符串类型);修改参数时,还要手动拼接?和&,容易出错。
原生解法
URL 与 URLSearchParams 是浏览器原生的URL 处理 API(URL MDN、URLSearchParams MDN),能自动处理:
- URL 参数的读取/修改/删除;
- 中文/特殊字符的编码/解码;
- URL 的拼接/解析。
// 解析URL
const url = new URL('https://example.com?name=张三&age=20')
// 读取参数(自动解码中文)
console.log(url.searchParams.get('name')) // 张三
console.log(url.searchParams.get('age')) // "20"(字符串)
// 修改参数
url.searchParams.set('age', '21') // 把age改为21
url.searchParams.append('gender', 'male') // 添加gender参数
// 生成新URL
console.log(url.href) // https://example.com?name=张三&age=21&gender=maleVue3 实战示例(分页参数处理)
在 Vue 的分页组件中,动态修改 URL 参数并跳转:
<script setup lang="ts">
import { useRouter } from 'vue-router'
const router = useRouter()
// 跳转到指定页码
const goToPage = (page: number) => {
// 当前URL
const currentUrl = new URL(window.location.href)
// 修改page参数
currentUrl.searchParams.set('page', page.toString())
// 跳转(保持SPA体验)
router.push(currentUrl.href)
}
</script>
<template>
<div class="pagination">
<button @click="goToPage(1)">1</button>
<button @click="goToPage(2)">2</button>
<button @click="goToPage(3)">3</button>
</div>
</template>
<style scoped>
.pagination button {
margin: 0 5px;
padding: 5px 10px;
border: 1px solid #007bff;
border-radius: 4px;
background: #fff;
cursor: pointer;
}
.pagination button:hover {
background: #007bff;
color: #fff;
}
</style>优势
- 无正则:避免正则的模糊匹配(比如
page=2不会被误判为page2); - 自动编码:中文参数自动转成
%E5%BC%A0%E4%B8%89,读取时自动解码; - 类型安全:参数值始终为字符串(需手动转成 number)。
兼容性
URL:Chrome 32+、Firefox 26+、Safari 7.1+(caniuse);URLSearchParams:Chrome 49+、Firefox 44+、Safari 10.1+(caniuse)。
9. requestAnimationFrame:动画与屏幕同步的“时间大师”
痛点
用 setTimeout 做动画,要么掉帧(时间间隔不准),要么页面卡成 PPT——因为setTimeout的回调会被主线程的其他任务阻塞,无法同步屏幕刷新率(60fps)。
原生解法
requestAnimationFrame(简称 RAF)是浏览器原生的动画调度器(MDN 链接),能在屏幕刷新前执行回调,确保动画与刷新率同步(每 16.67ms 执行一次)。
let progress = 0 // 动画进度(0~100)
const bar = document.querySelector('.progress-bar')
function animate() {
progress += 1
bar.style.width = `${progress}%`
// 若未完成,继续下一帧
if (progress < 100) {
requestAnimationFrame(animate)
}
}
// 启动动画
requestAnimationFrame(animate)Vue3 实战示例(进度条动画)
在 Vue 中实现一个同步屏幕刷新率的进度条:
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
const progressBar = ref<HTMLDivElement | null>(null)
let progress = ref(0)
let animationId = ref<number | null>(null)
// 启动进度条动画
const startProgress = () => {
if (!progressBar.value) return
const animate = () => {
progress.value += 1
progressBar.value.style.width = `${progress.value}%`
// 未完成则继续
if (progress.value < 100) {
animationId.value = requestAnimationFrame(animate)
}
}
animationId.value = requestAnimationFrame(animate)
}
// 组件卸载时取消动画(避免内存泄漏)
onUnmounted(() => {
if (animationId.value) {
cancelAnimationFrame(animationId.value)
}
})
</script>
<template>
<div class="progress-container">
<div
ref="progressBar"
class="progress-bar"
></div>
</div>
<button
@click="startProgress"
class="start-btn"
>
开始加载
</button>
</template>
<style scoped>
.progress-container {
width: 300px;
height: 20px;
border: 1px solid #eee;
border-radius: 10px;
overflow: hidden;
margin-bottom: 10px;
}
.progress-bar {
height: 100%;
width: 0;
background: #007bff;
transition: width 0.1s linear; /* 配合RAF的平滑动画 */
}
.start-btn {
padding: 5px 10px;
background: #28a745;
color: #fff;
border: none;
border-radius: 4px;
cursor: pointer;
}
</style>优势
- 流畅:同步 60fps 刷新率,无掉帧;
- 节能:页面不可见时自动暂停(比如切换标签页);
- 安全:避免
setTimeout的时间误差(比如setTimeout(animate, 16)可能延迟)。
兼容性
- Chrome 24+、Firefox 23+、Safari 6.1+(caniuse 链接)。
10. 顶层 await:模块级异步初始化的“简化器”
痛点
以前模块异步加载配置(比如config.json),得写:
// config.js
export const init = async () => {
const res = await fetch('/config.json')
return res.json()
}
// 使用时
import { init } from './config.js'
init().then((config) => console.log(config))调用链长,可读性差。
原生解法
ES2022 支持顶层 await(MDN 链接)——模块中可直接使用await,无需封装成async函数。
// config.js(ES Module)
const res = await fetch('/config.json')
const config = await res.json()
export default config
// 使用时(直接导入,自动等待配置加载完成)
import config from './config.js'
console.log(config.apiBaseUrl) // 直接使用Vue3 实战示例(异步加载配置)
在 Vue 的全局配置模块中,异步加载 API 地址:
// src/utils/config.ts(ES Module)
const fetchConfig = async () => {
const res = await fetch('/api/config')
if (!res.ok) throw new Error('配置加载失败')
return res.json()
}
// 顶层await:等待配置加载完成
const config = await fetchConfig()
export default config在 Vue 组件中使用:
<script setup lang="ts">
import config from '../utils/config'
import { ref, onMounted } from 'vue'
const userData = ref<any>(null)
onMounted(async () => {
// 使用配置中的API地址
const res = await fetch(`${config.apiBaseUrl}/user`)
userData.value = await res.json()
})
</script>
<template>
<div
v-if="userData"
class="user-card"
>
<h3>{{ userData.name }}</h3>
<p>年龄:{{ userData.age }}</p>
<p>邮箱:{{ userData.email }}</p>
</div>
</template>
<style scoped>
.user-card {
border: 1px solid #eee;
border-radius: 8px;
padding: 15px;
margin: 10px 0;
}
</style>优势
- 简化代码:无需
init函数和.then链; - 可读性高:代码线性执行,逻辑清晰;
- 支持动态导入:结合
import()实现按需加载(比如const config = await import('./config.ts'))。
兼容性
- Chrome 89+、Firefox 87+、Safari 15.4+(caniuse 链接)——需模块为 ES Module(
type="module")。
总结:现代前端的“轻量级”选择
回顾这 10 个帮你“少写代码”的原生 API,它们不是“花里胡哨的新特性”,而是浏览器为解决前端痛点专门设计的效率工具:
为什么要选原生 API?
- 更轻:不用引入体积庞大的工具库(比如
moment.js≈70KB,Intl是浏览器内置,0 体积); - 更快:浏览器层面的优化(比如
IntersectionObserver无重排,requestAnimationFrame同步刷新率); - 更稳:原生 API 由浏览器维护,兼容性持续提升(比如
Set覆盖 99%现代浏览器); - 更易维护:代码逻辑更简洁(比如
const unique = [...new Set(arr)]比filter+indexOf好懂 10 倍)。
最后一句话:别再“为了用库而用库”
前端发展到今天,“轻量级”才是王道——卸掉冗余的工具库,用原生 API 解决问题,你会发现代码变简洁了,性能变好了,维护成本也降低了。
下次遇到“去重”“URL 解析”“动画”这样的问题,先别急着找npm包或者手写循环——打开 MDN 查一查,说不定浏览器已经帮你做好了更优雅的解决方案。
- 本文链接:https://fridolph.top/posts/2025-07-05__jsApi
- 版权声明:本博客所有文章除特别声明外,均默认采用 CC BY-NC-SA 许可协议。