2025技术更新:1 可让你少写代码的浏览器原生API
你是不是还在手写 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 参数并传递给组件:
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,却被替换成默认值1const 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的0mergedConfig.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 的拼接/解析。
// 解析URLconst 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改为21url.searchParams.append('gender', 'male') // 添加gender参数
// 生成新URLconsole.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)。
兼容性
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),得写:
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 查一查,说不定浏览器已经帮你做好了更优雅的解决方案。
支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!