1.window.location.href 和 router.push 跳转方式

1. 冷跳转 (window.location.href) 的数据存活状态

概念:类似于浏览器的"断电重启"或"物理刷新"。跳转后,浏览器会完全卸载当前页面的上下文,并重新加载新页面。

  • 被彻底清空的(无法存活)
    • 所有存在"运行内存(JS Heap)"里的东西。
    • JS 声明的普通变量(let, const, var)、局部闭包变量。
    • JS 模块闭包(Module Cache)及其内部变量 。例如写在 .vue 文件 <script> 顶层、export default {} 外部的变量:let count = 0; let timer = null;
    • 整个 window 对象会被彻底销毁并替换 (浏览器会给新页面赋予一个全新的、原生的 window,旧 window 及挂载在上面的所有自定义属性全部清空)。
    • 框架状态(如 Vue 组件实例的状态,以及 Vuex 和 Pinia 里面的所有数据)。
    • DOM 树及绑定的原生事件。
    • 未执行完的定时器(setTimeoutsetInterval)。
  • 存活下来的(不受跳页影响)
    • 写入"磁盘/硬盘"或由浏览器底层接管的持久化数据。
    • sessionStorage(只要页签不关就一直存在)。
    • localStorageCookie
    • 浏览器的 HTTP 静态资源缓存(强缓存/协商缓存)。
    • URL 上的 Query 参数。

2. 热跳转 (Vue router.push) 的数据存活状态

概念 :单页面应用(SPA)内的无刷新跳转。本质是利用 History API 拦截跳转,仅仅是更换了页面上渲染的组件,JavaScript 执行环境没有被销毁(window 对象也没有被销毁)。

  • 被销毁的(无法存活)
    • 旧页面的 Vue 组件实例本身(触发 beforeDestroy/unmounted 钩子)。
    • 该组件内部的 datacomputed 等局部状态。
    • 该组件对应的 DOM 节点及组件级事件。
  • 依然存在的(存活下来)
    • window 对象及其上的所有全局变量。
    • JS 模块闭包(Module Cache)及其内部变量 。即声明在 .vue.js 文件顶层、export default {} 外部的模块级变量(例如:let count = 0; let timer = null;)。由于模块在一生中只解析一次,这些变量会像全局单例一样永远驻留内存。
    • Vuex / Pinia 的全局状态树。
    • 未清理的异步任务 (如 setTimeout,跳页后依然会在后台倒计时并在新页面触发回调,极易引发 Bug)。

3. 为什么大型混合应用(如本项目)大量使用 window.location.href

在 Vue 项目中放弃丝滑的热跳转,转而使用暴力的冷跳转,主要基于以下架构和业务痛点:

  1. 移动端客户端拦截限制(最核心原因) : 在 iOS/Android 内嵌 H5 (Hybrid App) 的场景下,客户端需要拦截 H5 的跳转请求以注入原生功能(如拦截带特定参数的 URL 以唤起扫一扫、支付,或改变原生导航栏)。Vue 的 router.push 不会发出真实的 HTTP 跳转请求,导致原生客户端(WebView)无法感知、无法拦截,从而导致大量端侧交互功能瘫痪。
  2. 微前端与多工程物理隔离 : 庞大的业务系统往往被拆分成多个独立的 Vue 项目(如商城独立部署,个人中心独立部署)。系统间的跨域跳转超出了单一 Vue Router 的管辖范围,必须通过 window.location.href 跨系统跳跃。
  3. 微信 JS-SDK 签名与兼容性: 在微信 Webview 中,SPA 的无刷新路由变化极易导致 URL 签名错乱(尤其是 iOS 微信),进而导致分享、支付等 SDK 功能失效。
  4. 防范内存泄漏: 在重度电商长列表、多媒体场景下,长期的热跳转会导致内存积压。冷跳转相当于强制垃圾回收(GC),保障低端机型的稳定性。

4. 单页应用 (SPA) 中的网络请求与"幽灵回调"

router.push 热跳转模式下,如果旧页面发起了网络请求且未完成时发生了页面跳转,会产生以下现象:

  • 请求依然在发送 :网络请求(如 Axios/Fetch 发起的 XMLHttpRequest 或 Fetch 动作)是由浏览器底层 API 代理的,并不依附于 Vue 组件实例。因此,跳页后该请求在后台继续存在并执行,直到服务器返回结果。
  • 回调诈尸(幽灵回调) :当请求成功返回时,原先写在旧页面的 .then() 回调函数会继续执行
  • 潜在危害 :如果回调中存在对全局状态(Vuex)的修改,或者使用了已经被销毁的 this 实例,会导致状态意外篡改、报错甚至页面崩溃。
  • 解决方案 :在 SPA 开发规范中,必须在组件销毁钩子(unmounted / beforeDestroy)中,利用 AbortController 或框架提供的 CancelToken,手动取消尚未完成的网络请求。

5. 模块级变量(闭包)在冷热跳转中的差异案例

为了更直观地理解,我们来看一段日常开发的源代码,以及它被打包后的底层形态。

1. 日常开发的 Vue 源代码:

javascript 复制代码
<script>
// ==== 危险地带:写在 export default 外面的模块级变量 ====
let count = 0; 
let timer = null;

export default {
  data() {
    return {
      // ==== 安全地带:写在组件实例内部的变量 ====
      localCount: 0 
    }
  },
  mounted() {
    count++;
    this.localCount++;
    console.log('外部 count:', count, '内部 localCount:', this.localCount);
  }
}
</script>

2. 经过 Webpack/Vite 打包后的真实形态(底层的闭包机制):

javascript 复制代码
// Webpack 在底层会将上面的代码包裹在一个独立的模块工厂函数中
function 模块工厂_组件A(module, exports) {
    // 您的 count 和 timer 被关在这个模块的专属闭包里
    let count = 0; 
    let timer = null;
    
    // 导出您的 Vue 组件配置对象
    module.exports = {
        data() { return { localCount: 0 } },
        mounted() {
            count++;
            this.localCount++;
        }
    }
}
  • 背后的机制(Webpack Module Cache) : 浏览器执行时,Webpack/Vite 会维护一个模块缓存表(Module Cache) 。一个模块(即上述的模块工厂函数)在一生中只会被执行一次,随后其导出结果会被缓存在内存中。写在最外层的变量,实际上就是存在于这个模块工厂的闭包中,成为了事实上的"全局单例"。
  • 在热跳转 (router.push) 中 : 由于 window 和 JS 内存未被销毁,Webpack 的模块缓存表依然存在。当用户再次跳回该页面时,Webpack 查表发现它执行过了,于是不会重新执行 模块工厂代码。因此,闭包中的 counttimer依然存在并保持旧值。这极易导致计数器疯狂累加、或后台存在无数个未被清理的幽灵定时器(引发严重内存泄漏)。
  • 在冷跳转 (window.location.href) 中 : 整个页面"断电重启",Webpack 的模块缓存表被彻底撕毁。当新页面加载时,模块会被视为首次引入,重新执行闭包代码,counttimer 会被彻底销毁并重新初始化,回归最初干净的状态。
相关推荐
还有多久拿退休金9 小时前
LLM应用开发一:给失忆的大模型装上"脑子"——LangChain.js对话记忆从零实战
前端·llm
ZengLiangYi9 小时前
插件式架构设计:SourceAdapter 接口抽象
前端·javascript·后端
万岳科技系统开发9 小时前
私域直播系统开发从0到1:企业直播平台搭建全过程
前端·小程序·架构
出海小龙9 小时前
联盟营销实战技能体系:从市场研究到数据优化的完整盈利框架
大数据·前端·人工智能
code_Bo9 小时前
apple gpt 礼品卡订阅失败解决方案
前端·人工智能·后端
转转技术团队10 小时前
MCP 解析:给 AI 装上“万能充电口”,打通连接世界的“最后一公里”
前端
Y敲键盘的地方10 小时前
第9章 工具调用循环——Agent的行动闭环
java·服务器·前端
苏瞳儿10 小时前
vue3+pinia+mqtt实时响应连接
前端·javascript·vue.js
ayqy贾杰10 小时前
我同事,40了,他vibe coding了个App
前端·ios·客户端