必须用 deepFreeze 递归冻结静态路由表,因 Object.freeze() 仅冻结第一层;需遍历所有自有键(含 Symbol 和不可枚举属性),对嵌套对象先递归冻结再冻结父对象,并用 WeakMap 防循环引用;冻结须在模块顶层立即执行并导出冻结结果,配合 TypeScript readonly、ESLint 规则及框架约束提升防护力。

直接用 Object.freeze() 冻结静态路由表,只能锁住第一层属性,而真实路由配置(如 children、meta、props)几乎全是嵌套对象——不递归冻结,就等于没锁。
必须用 deepFreeze 递归处理所有嵌套层级
前端路由表常见结构含多层 children、meta、component 等嵌套对象,仅调用 Object.freeze(routes) 后,仍可执行:routes[0].children[1].meta.auth = true 或 routes[0].props.id = 'new' —— 这些操作完全成功,因为子对象未被冻结。
- 遍历所有自有键(包括 Symbol 和不可枚举属性),用
Reflect.ownKeys(obj)替代Object.keys() - 对每个值判断:
val !== null && typeof val === 'object' && !(val instanceof Date || val instanceof RegExp || val instanceof ArrayBuffer) - 满足条件则先递归冻结
deepFreeze(val),再冻结父对象(“先子后父”,确保深层已不可变)
用 WeakMap 防止循环引用导致崩溃
某些路由配置可能包含自引用(如 redirect: { name: 'home' } 指向自身)、或通过 meta.parent 构建树形结构,若不做防护,递归会无限深入直至栈溢出。
完整的Galileo AI平台 TypeScript/JS SDK 参考,用于评估、观察和保护 GenAI 应用。适用于构建 Node.js 或 TypeScript 项目。
- 初始化
const seen = new WeakMap(),键为原始对象,值为冻结后结果 - 进入函数时先检查
if (seen.has(obj)) return seen.get(obj) - 在冻结前调用
seen.set(obj, obj),冻结后再更新为seen.set(obj, Object.freeze(obj))
冻结时机必须早于任何模块导入和使用
如果路由表导出后再冻结(例如 export default Object.freeze(routes)),或某个页面组件先 import { routes } from './router' 再调用 deepFreeze(routes),其他模块拿到的仍是未冻结的原始引用——冻结彻底失效。
立即学习“前端免费学习笔记(深入)”;
- 在模块顶层立即执行:定义完路由数组后,马上调用
deepFreeze(routes) - 导出时只暴露冻结结果:
export const ROUTES = deepFreeze([...]),不导出原始变量名 - 避免构建工具干扰:不要写成
export default deepFreeze({...}),部分打包器可能提前解析未冻结版本
配合框架与开发约束提升实际防护力
冻结是运行时防线,需叠加静态检查与约定才能真正防住误改。
- 在 Vue Router 的
createRouter({ routes: ROUTES })中直接传入冻结后的数组,避免内部响应式系统尝试劫持 - 搭配 TypeScript 的
readonly类型断言:const ROUTES: readonly RouteRecordRaw[] = deepFreeze([...]) - 启用 ESLint 规则
no-param-reassign和no-const-assign,编辑期就拦截ROUTES[0].path = ''类赋值 - 对动态生成的路由(如权限过滤后拼接),应在拼接完成后再次调用
deepFreeze,而非只冻初始模板


















