2025技术更新:1 可让你少写代码的浏览器原生API

5315 字
27 分钟
2025技术更新:1 可让你少写代码的浏览器原生API

你是不是还在手写 filter+indexOf 去重?还在为 URL 参数解析烦恼? 开发新项目总在 utils 里复制粘贴?

其实,现代浏览器早已内置了更优雅的解决方案——随着 IE 彻底退出历史舞台,曾经“不敢用”的原生 API,如今已成为提升开发效率的“秘密武器”。以下 10 个技巧,每一个都能帮你少写几十行代码,同时让代码更高效、更易维护。

省流上重点:

  1. 🗑️ Set:数组去重+防重绑定,代替Lodash.uniq
  2. 🗑️ Set:数组去重+防重绑定,代替Lodash.uniq
  3. ↔️ Object.entries()+Object.fromEntries():对象与数组互转,告别for...in循环;
  4. 🎯 ??+??=:精准空值判断,避免||的误判;
  5. 🌍 Intl API:原生国际化,代替moment.js
  6. 🔍 IntersectionObserver:高性能懒加载,代替scroll事件;
  7. 🛡️ Promise.allSettled():批量请求容错,不用再怕“一错全灭”;
  8. 🧭 element.closest():安全查找祖先,告别parentNode嵌套;
  9. 🔗 URL+URLSearchParams:URL 操作现代化,告别正则拼接;
  10. ⏱️ requestAnimationFrame:流畅动画,比setTimeout丝滑 10 倍;
  11. 顶层await:模块异步简化,不用再写init().then()

1. Set:新增集合 API#

Set 是 ES6 引入的“唯一值集合”,天然支持快速去重唯一性判断

Set MDN链接

痛点#

传统 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():对象与数组的“双向门”#

MDN entries MDN fromEntries

痛点#

处理对象时,需要手动遍历 Object.keysObject.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...inhasOwnProperty
  • 支持编码:自动处理 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件”的逻辑错误。

原生解法:用??精准区分“空值”与“合法假值”#

??(空值合并运算符)仅在左侧为nullundefined时返回右侧值,完美避开0''false等合法假值;
??=(空值赋值运算符)仅在左侧为nullundefined时赋值,相当于“若未设置,则初始化”。

针对性示例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>

为什么这两个示例更有价值?#

  1. 场景真实:覆盖电商、组件开发等高频业务场景,直接解决实际问题;
  2. 对比强烈:通过注释点明||的缺陷,突出??/??=的优势;
  3. 逻辑清晰:用computedref保持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/””的场景,直接掏出????=——这才是现代前端处理空值的“正确姿势”!

兼容性#

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 事件的频繁触发;
  • 无重排:浏览器层面优化,不影响渲染;
  • 场景广:支持图片懒加载、元素曝光统计(比如广告曝光量)、无限滚动。

兼容性#

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.parentNodetarget.closest('.modal'));
  • 灵活:支持任何 CSS 选择器(类、ID、属性选择器等)。

兼容性#

  • Chrome 41+、Firefox 35+、Safari 9+(caniuse 链接),覆盖 99%现代浏览器。

8. URL + URLSearchParams:URL 操作的“现代化工具箱”#

痛点#

手写正则解析 URL 参数,要么漏处理中文编码,要么把page=2解析成page: "2"(字符串类型);修改参数时,还要手动拼接?&,容易出错。

原生解法#

URLURLSearchParams 是浏览器原生的URL 处理 APIURL MDNURLSearchParams 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=male

Vue3 实战示例(分页参数处理)#

在 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)可能延迟)。

兼容性#

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 支持顶层 awaitMDN 链接)——模块中可直接使用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 查一查,说不定浏览器已经帮你做好了更优雅的解决方案。

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!

2025技术更新:1 可让你少写代码的浏览器原生API
https://blog.fridolph.top/posts/2025-07-05__jsapi/
作者
Fridolph
发布于
2025-07-05
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
Fridolph
热爱 Coding、音乐和羽毛球的 90 后全栈工程师
公告
欢迎访问我的小站 ^_^ 我是昇哥,热爱Coding,喜爱音乐、羽毛球和摄影的 90后全栈工程师
分类
标签

文章目录