V1.6.1性能优化:高频路径提速与代码精简

v1.6.1 针对 isSameQueryObject.freeze 两处高频执行路径进行优化,减少不必要的计算开销与重复代码

前言

v1.6.0 引入了 params 页面参数传递和查询参数增强方法,功能更加完整。但在实际使用中,路由状态管理存在两处可优化的细节:

  1. isSameQuery 缺少空对象快速路径 --- 每次导航、守卫执行、状态同步都会调用 isSameQuery,而空对象是常见场景
  2. Object.freeze 逻辑分散 --- metaqueryparams 的冻结逻辑分散在 setCurrentRoutecreateStartLocation 中,存在重复代码

v1.6.1 针对这两处进行优化,在不改变任何外部行为的前提下提升性能和可维护性。


一、isSameQuery 空对象快速路径

问题分析

isSameQuery 在路由器内部被高频调用:

  • setCurrentRoute 中判断 fromto 是否相同
  • syncCurrentRoute 中比较当前查询参数
  • isSameRouteLocation 中作为路由位置比较的一部分

原始实现无论查询参数是否为空对象,都会执行 Object.keys()every() 遍历:

typescript 复制代码
// 优化前
private isSameQuery(a: Record<string, string>, b: Record<string, string>): boolean {
    const keysA = Object.keys(a)
    const keysB = Object.keys(b)
    if (keysA.length !== keysB.length) return false
    return keysA.every(key => a[key] === b[key])
}

在空对象场景下(如首页导航、无参数跳转),Object.keys({}) 返回空数组,every 对空数组返回 true,虽然结果正确,但产生了不必要的函数调用开销。

优化方案

添加两层快速路径:

typescript 复制代码
// 优化后
private isSameQuery(a: Record<string, string>, b: Record<string, string>): boolean {
    if (a === b) return true              // 快速路径 1:引用相等
    const keysA = Object.keys(a)
    const keysB = Object.keys(b)
    if (keysA.length !== keysB.length) return false
    if (keysA.length === 0) return true   // 快速路径 2:双空对象
    return keysA.every(key => a[key] === b[key])
}

快速路径 1:引用相等(a === b

ab 是同一个对象引用时,直接返回 true。这种情况在路由状态未变化时(如重复导航检测)经常出现。

快速路径 2:双空对象(keysA.length === 0

keysA.length === keysB.lengthkeysA.length === 0 时,说明两者都是空对象,直接返回 true。避免了 every 对空数组的调用。

性能影响

场景 优化前 优化后
引用相等 Object.keys + every 直接返回 true
双空对象 Object.keys × 2 + every Object.keys × 2 + 长度判断
非空对象 不受影响 不受影响

对于高频调用的导航场景(特别是无参数跳转),减少了不必要的 Object.keysevery 开销。


二、Object.freeze 逻辑集中化

问题分析

v1.6.0 中,metaqueryparams 的冻结逻辑分散在两处:

  1. setCurrentRoute --- 手动冻结 metaquery
  2. createStartLocation --- 手动创建冻结的初始对象
typescript 复制代码
// 优化前:setCurrentRoute 中
function setCurrentRoute(route: RouteLocation): void {
	const from = currentRoute
	currentRoute = {
		path: route.path,
		name: route.name,
		meta: Object.freeze({ ...route.meta }), // 手动冻结
		query: Object.freeze({ ...route.query }), // 手动冻结
		params: Object.freeze({ ...route.params })
		// ...
	}
}

// 优化前:createStartLocation 中
export function createStartLocation(): RouteLocation {
	return {
		path: '/',
		meta: Object.freeze({}), // 手动冻结
		query: Object.freeze({}), // 手动冻结
		params: Object.freeze({}) // 手动冻结
		// ...
	}
}

这种分散存在两个问题:

  1. 重复代码 --- 冻结逻辑在多处重复,后续若需条件冻结(如开发模式跳过冻结),需改多处
  2. 不一致风险 --- 新增字段时容易遗漏冻结

优化方案

将冻结逻辑集中到 createRouteLocation 工厂函数中:

typescript 复制代码
// 优化后:createRouteLocation 中统一冻结
export function createRouteLocation(base: { path: string; name?: string; meta: RouteMeta; query: Record<string, string>; fullPath: string; params?: ParamObject; _synced?: boolean }): RouteLocation {
	const query = Object.freeze(base.query)
	const params: Readonly<ParamObject> = base.params ? Object.freeze({ ...base.params }) : Object.freeze({})
	return {
		path: base.path,
		name: base.name,
		meta: Object.freeze({ ...base.meta }),
		query,
		params
		// ...
	}
}

setCurrentRoutecreateStartLocation 不再手动冻结,统一委托给 createRouteLocation

typescript 复制代码
// 优化后:setCurrentRoute 中
function setCurrentRoute(route: RouteLocation): void {
	const from = currentRoute
	currentRoute = createRouteLocation({
		path: route.path,
		name: route.name,
		meta: { ...route.meta },
		query: { ...route.query },
		fullPath: route.fullPath,
		params: route.params,
		...(route._synced !== undefined && { _synced: route._synced })
	})
	// ...
}

// 优化后:createStartLocation 中
export function createStartLocation(): RouteLocation {
	return createRouteLocation({
		path: '/',
		meta: {},
		query: {},
		fullPath: '/'
	})
}

优化收益

维度 优化前 优化后
冻结逻辑位置 setCurrentRoute + createStartLocation createRouteLocation 一处
代码行数 分散在多处 集中在工厂函数
后续维护 改冻结策略需改多处 只需改 createRouteLocation
一致性 手动保证 工厂函数自动保证

升级指南

v1.6.1 是纯内部优化,完全向后兼容,无需修改任何现有代码即可升级。

本次优化不改变任何公共 API 的行为和签名,所有变更仅限于内部实现细节。如果你正在使用 v1.6.0,直接升级到 v1.6.1 即可享受性能提升。

相关推荐
猩猩程序员2 小时前
将 LiteLLM 迁移到 Rust —— 构建最快、最轻量的 AI Gateway
前端
lichenyang4532 小时前
JSBridge 分发升级:为什么要从 if-else 变成 Registry > 这是「ASCF 架构升级」系列的第 3 篇
前端
码上天下2 小时前
流式响应断了,前端怎么自动重连续传
前端
anyup2 小时前
来简单聊聊鸿蒙开发,万元奖金的事~
前端·华为·harmonyos
北凉温华2 小时前
Univer 在线表格模块使用说明
前端
lichenyang4532 小时前
WebRuntimePage 拆分:从大页面到运行时控制器
前端
竹林8182 小时前
从报错到跑通:我用 @solana/web3.js 开发 Solana 钱包连接踩过的三个坑
前端
MariaH2 小时前
Node中操作MySQL
前端
还有多久拿退休金2 小时前
一个 var 让整个团队加班到凌晨——JS 闭包的那些暗坑
前端·javascript