深度解析Vue中的路由跳转$router.push VS location.href

 更新时间:2026年03月13日 09:10:32   作者:木易 士心  
在 Vue 单页应用(SPA)中,页面跳转是一个看似简单实则暗藏玄机的话题,本文将从底层原理、架构设计、源码实现等多个维度进行深度剖析,感兴趣的小伙伴可以跟随小编一起学习一下

前言

在 Vue 单页应用(SPA)中,页面跳转是一个看似简单实则暗藏玄机的话题。this.$router.push('/login')window.location.href = '/#/login' 表面上都能实现"跳转到登录页"的效果,但其背后的实现机制、对应用状态的影响、以及在不同场景下的行为差异,直接关系到应用的稳定性、用户体验和可维护性。

本文将从底层原理架构设计源码实现等多个维度进行深度剖析。

一、架构层面的根本差异

1.1 整体架构对比图

┌─────────────────────────────────────────────────────────────────────┐
│                         Vue 应用架构层面                              │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   ┌──────────────────────────────────────────────────────────────┐ │
│   │              this.$router.push('/login')                     │ │
│   │                                                              │ │
│   │   Vue Component ──► Vue Router ──► Route Matcher            │ │
│   │         │                 │               │                  │ │
│   │         │                 ▼               ▼                  │ │
│   │         │         Navigation Guards   Component              │ │
│   │         │              │            Lifecycle                │ │
│   │         │              ▼               │                     │ │
│   │         │         [权限校验]          ▼                      │ │
│   │         │         [数据预取]      DOM 更新                   │ │
│   │         │         [过渡动画]    (无刷新)                     │ │
│   │         ▼                                                  │ │
│   │    保持应用状态、Vuex store、事件监听器等                    │ │
│   └──────────────────────────────────────────────────────────────┘ │
│                                                                     │
│   ══════════════════════════════════════════════════════════════   │
│                        分 割 线                                     │
│   ══════════════════════════════════════════════════════════════   │
│                                                                     │
│   ┌──────────────────────────────────────────────────────────────┐ │
│   │           window.location.href = '/#/login'                  │ │
│   │                                                              │ │
│   │   Vue App ──✕──► Browser Navigation ──► Page Reload?        │ │
│   │                      │                     │                 │ │
│   │                      ▼                     ▼                 │ │
│   │              直接修改 URL            可能触发:              │ │
│   │              绕过 Vue Router        - 完整页面刷新           │ │
│   │                                      - 重新下载资源          │ │
│   │                                      - 丢失应用状态          │ │
│   │                                      - 重启 Vue 实例         │ │
│   └──────────────────────────────────────────────────────────────┘ │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

1.2 核心差异总结

维度$router.push()location.href
控制权Vue Router 完全控制浏览器原生控制
应用状态保持完整可能丢失
代码执行在 Vue 上下文中脱离 Vue 上下文
可预测性高(遵循路由配置)低(依赖浏览器行为)

二、Vue Router 的底层实现原理

2.1 Vue Router 架构图

┌────────────────────────────────────────────────────────────────────────┐
│                        Vue Router 内部架构                              │
├────────────────────────────────────────────────────────────────────────┤
│                                                                        │
│  ┌──────────────┐      ┌──────────────┐      ┌──────────────┐        │
│  │   Router     │      │    Route     │      │   Matcher    │        │
│  │   Instance   │◄────►│   Matcher    │◄────►│   (路由表)    │        │
│  └──────────────┘      └──────────────┘      └──────────────┘        │
│         │                      │                      │               │
│         │                      ▼                      ▼               │
│         │             ┌──────────────────┐   ┌─────────────────┐     │
│         │             │ Navigation       │   │ Route Record    │     │
│         │             │ Guards           │   │ { path, comp,   │     │
│         │             │ (beforeEach等)   │   │  children... }  │     │
│         │             └──────────────────┘   └─────────────────┘     │
│         │                      │                                      │
│         ▼                      ▼                                      │
│  ┌─────────────────────────────────────────────────────────┐         │
│  │                    History Mode                          │         │
│  │  ┌──────────────┐           ┌──────────────┐            │         │
│  │  │ Hash Mode    │           │ History Mode │            │         │
│  │  │ (hashchange) │           │ (pushState)  │            │         │
│  │  └──────────────┘           └──────────────┘            │         │
│  └─────────────────────────────────────────────────────────┘         │
│                                  │                                    │
│                                  ▼                                    │
│                        ┌──────────────────┐                          │
│                        │  View Updating   │                          │
│                        │  (<router-view>) │                          │
│                        └──────────────────┘                          │
│                                                                        │
└────────────────────────────────────────────────────────────────────────┘

2.2$router.push()的完整执行流程

┌─────────────────────────────────────────────────────────────────────┐
│                 $router.push() 完整执行流程                          │
└─────────────────────────────────────────────────────────────────────┘
                                │
                                ▼
        ┌──────────────────────────────────────────┐
        │  1. 参数标准化                           │
        │     push('/login') 或                    │
        │     push({ path: '/login', query: {} })  │
        └──────────────────────────────────────────┘
                                │
                                ▼
        ┌──────────────────────────────────────────┐
        │  2. 路由匹配                  │
        │     - 根据配置的 routes 数组匹配          │
        │     - 解析动态路由参数                   │
        │     - 处理嵌套路由                       │
        └──────────────────────────────────────────┘
                                │
                                ▼
        ┌──────────────────────────────────────────┐
        │  3. 触发导航守卫                         │
        │  ┌────────────────────────────────────┐  │
        │  │ 3.1 失活组件的 beforeRouteLeave    │  │
        │  └────────────────────────────────────┘  │
        │                  │                       │
        │                  ▼                       │
        │  ┌────────────────────────────────────┐  │
        │  │ 3.2 全局 beforeEach               │  │
        │  │     (权限校验、登录拦截等)         │  │
        │  └────────────────────────────────────┘  │
        │                  │                       │
        │                  ▼                       │
        │  ┌────────────────────────────────────┐  │
        │  │ 3.3 重用组件的 beforeRouteUpdate   │  │
        │  └────────────────────────────────────┘  │
        │                  │                       │
        │                  ▼                       │
        │  ┌────────────────────────────────────┐  │
        │  │ 3.4 路由配置的 beforeEnter         │  │
        │  └────────────────────────────────────┘  │
        │                  │                       │
        │                  ▼                       │
        │  ┌────────────────────────────────────┐  │
        │  │ 3.5 激活组件的 beforeRouteEnter    │  │
        │  └────────────────────────────────────┘  │
        └──────────────────────────────────────────┘
                                │
                                ▼
        ┌──────────────────────────────────────────┐
        │  4. 导航确认                             │
        │     - 所有守卫都调用 next() 才会继续     │
        │     - 任一守卫调用 next(false) 则取消    │
        │     - 可重定向到其他路由                 │
        └──────────────────────────────────────────┘
                                │
                                ▼
        ┌──────────────────────────────────────────┐
        │  5. 更新 URL (根据路由模式)              │
        │  ┌────────────────────────────────────┐  │
        │  │ Hash Mode:                        │  │
        │  │   window.location.hash = '#/login'│  │
        │  │   (触发 hashchange 事件)           │  │
        │  └────────────────────────────────────┘  │
        │  ┌────────────────────────────────────┐  │
        │  │ History Mode:                     │  │
        │  │   history.pushState({}, '', url)  │  │
        │  │   (不触发页面刷新)                 │  │
        │  └────────────────────────────────────┘  │
        └──────────────────────────────────────────┘
                                │
                                ▼
        ┌──────────────────────────────────────────┐
        │  6. 更新应用状态                         │
        │     - 更新 this.$route 响应式对象        │
        │     - 触发 <router-view> 重新渲染        │
        │     - 执行组件生命周期钩子               │
        └──────────────────────────────────────────┘
                                │
                                ▼
        ┌──────────────────────────────────────────┐
        │  7. 执行 afterEach 钩子                  │
        │     (可用于分析、进度条结束等)           │
        └──────────────────────────────────────────┘
                                │
                                ▼
                        ┌──────────────┐
                        │   导航完成   │
                        └──────────────┘

2.3 源码级别的关键实现

// Vue Router 简化版源码示意
class VueRouter {
  push(location, onComplete, onAbort) {
    // 1. 标准化 location 参数
    location = normalizeLocation(location, this.currentRoute);
    
    // 2. 匹配路由记录
    const route = this.matcher.match(location);
    
    // 3. 确认导航(执行导航守卫)
    this.confirmTransition(route, () => {
      // 4. 更新当前路由
      this.updateRoute(route);
      
      // 5. 更新 URL
      this.ensureURL();
      
      // 6. 触发回调
      onComplete && onComplete(route);
    }, onAbort);
  }
  
  confirmTransition(route, onComplete, onAbort) {
    // 提取所有需要执行的导航守卫
    const queue = [].concat(
      // 失活组件的 beforeRouteLeave
      extractLeaveGuards(this.currentRoute),
      // 全局 beforeEach
      this.router.beforeHooks,
      // 重用组件的 beforeRouteUpdate
      extractUpdateHooks(this.currentRoute),
      // 路由配置的 beforeEnter
      route.matched.flatMap(m => m.beforeEnter),
      // 激活组件的 beforeRouteEnter
      extractEnterGuards(route)
    );
    
    // 顺序执行守卫
    runQueue(queue, (guard, next) => {
      guard(route, this.currentRoute, (to) => {
        if (to === false) {
          // 取消导航
          onAbort();
        } else if (isRoute(to)) {
          // 重定向
          this.push(to);
        } else {
          // 继续下一个守卫
          next();
        }
      });
    }, onComplete);
  }
}

三、Hash 模式 vs History 模式深度解析

3.1 Hash 模式原理

┌─────────────────────────────────────────────────────────────────────┐
│                       Hash 模式工作原理                              │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   URL 结构: https://example.com/#/user/profile?id=123              │
│             └────────────────────┘ └──────────────────────┘       │
│                   基础 URL              hash 部分                  │
│                                                                     │
│   ┌──────────────────────────────────────────────────────────────┐ │
│   │  特点:                                                        │ │
│   │  1. hash 变化不会触发页面刷新                                 │ │
│   │  2. hash 不属于 URL 路径,不会被发送到服务器                  │ │
│   │  3. 通过 hashchange 事件监听变化                              │ │
│   │  4. 兼容性好,无需服务器配置                                  │ │
│   └──────────────────────────────────────────────────────────────┘ │
│                                                                     │
│   事件监听流程:                                                     │
│   ┌──────────┐    hash变化    ┌─────────────┐    触发     ┌─────┐│
│   │ 浏览器   │ ─────────────► │ hashchange  │ ──────────► │Vue  ││
│   │ 地址栏   │                │   事件      │             │Router││
│   └──────────┘                └─────────────┘             └─────┘│
│                                                                     │
│   Vue Router 初始化:                                                │
│   ┌──────────────────────────────────────────────────────────────┐ │
│   │ window.addEventListener('hashchange', () => {                │ │
│   │   this.transitionTo(window.location.hash.slice(1));          │ │
│   │ });                                                          │ │
│   └──────────────────────────────────────────────────────────────┘ │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

3.2 History 模式原理

┌─────────────────────────────────────────────────────────────────────┐
│                     History 模式工作原理                             │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   URL 结构: https://example.com/user/profile?id=123                │
│             └────────────────────────┘└──────────────────┘         │
│                    完整路径都是真实 URL                              │
│                                                                     │
│   ┌──────────────────────────────────────────────────────────────┐ │
│   │  核心API:                                                     │ │
│   │  - history.pushState(state, title, url)  // 添加历史记录     │ │
│   │  - history.replaceState(state, title, url) // 替换当前记录   │ │
│   │  - popstate 事件  // 监听前进/后退                           │ │
│   └──────────────────────────────────────────────────────────────┘ │
│                                                                     │
│   ┌──────────────────────────────────────────────────────────────┐ │
│   │  关键特点:                                                    │ │
│   │  1. URL 更美观,没有 # 号                                    │ │
│   │  2. 需要服务器配置支持(所有路径都返回 index.html)          │ │
│   │  3. pushState/replaceState 不会触发页面刷新                  │ │
│   │  4. popstate 事件仅在浏览器前进/后退时触发                   │ │
│   └──────────────────────────────────────────────────────────────┘ │
│                                                                     │
│   服务器配置示例:                                                   │
│   ┌──────────────────────────────────────────────────────────────┐ │
│   │ server {                                                     │ │
│   │   location / {                                               │ │
│   │     try_files $uri $uri/ /index.html;                        │ │
│   │   }                                                          │ │
│   │ }                                                            │ │
│   └──────────────────────────────────────────────────────────────┘ │
│                                                                     │
│   ⚡  致命陷阱:                                                     │
│   ┌──────────────────────────────────────────────────────────────┐ │
│   │ 如果在 History 模式下使用:                                    │ │
│   │   window.location.href = '/#/login'                         │ │
│   │                                                              │ │
│   │ 实际会跳转到: https://example.com/#/login                    │ │
│   │ 这与你的 History 模式路由 /login 完全不匹配!                │ │
│   └──────────────────────────────────────────────────────────────┘ │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

3.3 两种模式的路由模式选择决策树

                        开始选择路由模式
                              │
                              ▼
                    ┌─────────────────┐
                    │ 是否需要美观URL │
                    │   (无 # 号)?    │
                    └─────────────────┘
                              │
                 ┌────────────┴────────────┐
                 │                         │
                 ▼                         ▼
                否                         是
                 │                         │
                 ▼                         ▼
        ┌──────────────┐         ┌─────────────────┐
        │  Hash Mode   │         │ 是否能控制      │
        │  (简单省事)  │         │ 服务器配置?    │
        └──────────────┘         └─────────────────┘
                                          │
                            ┌─────────────┴─────────────┐
                            │                           │
                            ▼                           ▼
                           否                          是
                            │                           │
                            ▼                           ▼
                  ┌──────────────────┐        ┌──────────────────┐
                  │   Hash Mode      │        │  History Mode    │
                  │ (无服务器权限时) │        │  (最佳体验)      │
                  └──────────────────┘        └──────────────────┘

四、window.location.href的执行机制

4.1 浏览器导航流程

┌─────────────────────────────────────────────────────────────────────┐
│              window.location.href = '/login' 执行流程               │
└─────────────────────────────────────────────────────────────────────┘
                                │
                                ▼
        ┌──────────────────────────────────────────┐
        │  1. 浏览器解析新 URL                     │
        │     - 解析协议、域名、端口、路径         │
        └──────────────────────────────────────────┘
                                │
                                ▼
        ┌──────────────────────────────────────────┐
        │  2. 判断是否需要刷新页面                 │
        │  ┌────────────────────────────────────┐  │
        │  │ 需要刷新的情况:                    │  │
        │  │ - 协议不同       │  │
        │  │ - 域名/端口不同                    │  │
        │  │ - 完整路径变化(非 hash 部分)     │  │
        │  └────────────────────────────────────┘  │
        │  ┌────────────────────────────────────┐  │
        │  │ 不刷新的情况:                      │  │
        │  │ - 仅 hash 部分变化                 │  │
        │  │   (如 /page → /page#section)      │  │
        │  └────────────────────────────────────┘  │
        └──────────────────────────────────────────┘
                                │
                    ┌───────────┴───────────┐
                    │                       │
                    ▼                       ▼
            需要刷新                仅 Hash 变化
                    │                       │
                    ▼                       ▼
    ┌────────────────────────┐    ┌─────────────────────┐
    │  3a. 完整页面刷新      │    │  3b. 仅滚动到锚点   │
    │  ┌──────────────────┐  │    │  或触发 hashchange │
    │  │ - 卸载当前页面   │  │    │                     │
    │  │ - 清除所有状态   │  │    │  ⚡ Vue Router      │
    │  │   (Vuex、事件等) │  │    │  无法感知此变化!   │
    │  │ - 发起新HTTP请求 │  │    │                     │
    │  │ - 重新加载资源   │  │    └─────────────────────┘
    │  │ - 重新初始化 Vue │  │
    │  └──────────────────┘  │
    └────────────────────────┘
                    │
                    ▼
        ┌──────────────────────────────────────────┐
        │  4. 更新浏览器历史记录                   │
        │     (可通过后退按钮返回)                 │
        └──────────────────────────────────────────┘

4.2 关键问题:绕过 Vue Router 的后果

场景:History 模式下执行

window.location.href = '/login'

问题分析:

浏览器行为: 

1. 检测到完整路径变化(从 /home 到 /login)

2. 触发页面刷新

3. 向服务器请求 /login 资源

服务器响应: 

  • 如果配置了 SPA fallback → 返回 index.html → Vue 重新启动
  • 如果未配置 → 返回 404 

Vue 应用状态: 

  • Vuex store 被重置
  • 所有组件实例销毁 
  • 事件监听器清除

定时器、WebSocket 等需要重新建立

五、导航守卫完整流程图

5.1 导航守卫执行顺序

┌─────────────────────────────────────────────────────────────────────┐
│                     导航守卫完整执行顺序                             │
│                  (从 /home 导航到 /user/profile)                    │
└─────────────────────────────────────────────────────────────────────┘
    触发导航: this.$router.push('/user/profile')
         │
         ▼
┌─────────────────────────────────────────────────────────────────────┐
│ 【阶段一:离开当前路由】                                             │
│                                                                     │
│   1. beforeRouteLeave (Home组件内)                                 │
│      ┌──────────────────────────────────────────────────────────┐  │
│      │ 用途:防止用户未保存就离开、清理定时器等                   │  │
│      │ 示例:                                                    │  │
│      │   beforeRouteLeave(to, from, next) {                     │  │
│      │     if (this.hasUnsavedChanges) {                        │  │
│      │       const confirm = window.confirm('确定离开?');      │  │
│      │       if (!confirm) return next(false);                  │  │
│      │     }                                                    │  │
│      │     next();                                              │  │
│      │   }                                                      │  │
│      └──────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────────┘
         │
         ▼
┌─────────────────────────────────────────────────────────────────────┐
│ 【阶段二:全局前置守卫】                                             │
│                                                                     │
│   2. beforeEach (全局注册)                                         │
│      ┌──────────────────────────────────────────────────────────┐  │
│      │ 用途:权限验证、登录检查、路由拦截                        │  │
│      │ 示例:                                                    │  │
│      │   router.beforeEach((to, from, next) => {                │  │
│      │     const isLoggedIn = store.state.isLoggedIn;           │  │
│      │     if (to.meta.requiresAuth && !isLoggedIn) {           │  │
│      │       next({ path: '/login', query: { redirect: to.fullPath } });│
│      │     } else {                                             │  │
│      │       next();                                            │  │
│      │     }                                                    │  │
│      │   });                                                    │  │
│      └──────────────────────────────────────────────────────────┘  │
│                                                                     │
│   3. beforeResolve (全局,在所有组件内守卫之后)                    │
│      ┌──────────────────────────────────────────────────────────┐  │
│      │ 用途:导航确认前的最后检查                                │  │
│      └──────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────────┘
         │
         ▼
┌─────────────────────────────────────────────────────────────────────┐
│ 【阶段三:路由独享守卫】                                             │
│                                                                     │
│   4. beforeEnter (路由配置中)                                      │
│      ┌──────────────────────────────────────────────────────────┐  │
│      │ 用途:特定路由的权限控制、数据预加载                      │  │
│      │ 示例:                                                    │  │
│      │   {                                                      │  │
│      │     path: '/admin',                                      │  │
│      │     component: Admin,                                    │  │
│      │     beforeEnter: (to, from, next) => {                   │  │
│      │       if (store.state.user.role !== 'admin') {           │  │
│      │         next('/403');                                    │  │
│      │       } else {                                           │  │
│      │         next();                                          │  │
│      │       }                                                  │  │
│      │     }                                                    │  │
│      │   }                                                      │  │
│      └──────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────────┘
         │
         ▼
┌─────────────────────────────────────────────────────────────────────┐
│ 【阶段四:组件内守卫】                                               │
│                                                                     │
│   5. beforeRouteEnter (目标组件 User)                              │
│      ┌──────────────────────────────────────────────────────────┐  │
│      │ ⚡ 注意:此时组件实例还未创建,无法访问 this             │  │
│      │ 用途:获取路由数据,通过回调访问组件实例                  │  │
│      │ 示例:                                                    │  │
│      │   beforeRouteEnter(to, from, next) {                     │  │
│      │     // ✘ 错误:this 未定义                              │  │
│      │     // this.loadUser();                                  │  │
│      │                                                          │  │
│      │     // ✔ 正确:通过回调访问实例                         │  │
│      │     next(vm => {                                         │  │
│      │       vm.loadUser(to.params.id);                         │  │
│      │     });                                                  │  │
│      │   }                                                      │  │
│      └──────────────────────────────────────────────────────────┘  │
│                                                                     │
│   6. beforeRouteUpdate (当路由参数变化时复用组件)                  │
│      ┌──────────────────────────────────────────────────────────┐  │
│      │ 场景:/user/1 → /user/2 (同一个 User 组件)              │  │
│      │ 示例:                                                    │  │
│      │   beforeRouteUpdate(to, from, next) {                    │  │
│      │     this.loadUser(to.params.id);                         │  │
│      │     next();                                              │  │
│      │   }                                                      │  │
│      └──────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────────┘
         │
         ▼
┌─────────────────────────────────────────────────────────────────────┐
│ 【阶段五:导航确认】                                                 │
│                                                                     │
│   所有守卫都调用 next() → 导航确认                                  │
│   任一守卫调用 next(false) → 导航取消                               │
│   任一守卫调用 next('/path') → 重定向                               │
└─────────────────────────────────────────────────────────────────────┘
         │
         ▼
┌─────────────────────────────────────────────────────────────────────┐
│ 【阶段六:更新视图】                                                 │
│                                                                     │
│   1. 失活 Home 组件 → beforeDestroy → destroyed                    │
│   2. 激活 User 组件 → beforeCreate → created → beforeMount → mounted│
│   3. 更新 <router-view>                                            │
└─────────────────────────────────────────────────────────────────────┘
         │
         ▼
┌─────────────────────────────────────────────────────────────────────┐
│ 【阶段七:全局后置钩子】                                             │
│                                                                     │
│   afterEach (全局注册)                                             │
│   ┌──────────────────────────────────────────────────────────────┐ │
│   │ 用途:进度条结束、页面标题设置、埋点统计                     │ │
│   │ 示例:                                                       │ │
│   │   router.afterEach((to, from) => {                          │ │
│   │     NProgress.done();                                       │ │
│   │     document.title = to.meta.title || 'My App';             │ │
│   │     analytics.trackPageView(to.fullPath);                   │ │
│   │   });                                                       │ │
│   └──────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
         │
         ▼
       导航完成

5.2 守卫对比:$router.pushvslocation.href

┌─────────────────────────────────────────────────────────────────────┐
│                    导航守卫触发对比                                  │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   this.$router.push('/login')                                      │
│   ┌──────────────────────────────────────────────────────────────┐ │
│   │ ✔ beforeRouteLeave    → 触发                               │ │
│   │ ✔ beforeEach          → 触发                               │ │
│   │ ✔ beforeEnter         → 触发                               │ │
│   │ ✔ beforeRouteEnter    → 触发                               │ │
│   │ ✔ afterEach           → 触发                               │ │
│   │ ✔ 组件生命周期         → 正常执行                          │ │
│   └──────────────────────────────────────────────────────────────┘ │
│                                                                     │
│   window.location.href = '/#/login' (Hash 模式)                    │
│   ┌──────────────────────────────────────────────────────────────┐ │
│   │ ✘ beforeRouteLeave    → 不触发                             │ │
│   │ ✘ beforeEach          → 不触发                             │ │
│   │ ✘ beforeEnter         → 不触发                             │ │
│   │ ✘ beforeRouteEnter    → 不触发                             │ │
│   │ ✘ afterEach           → 不触发                             │ │
│   │ ⚡  可能触发 hashchange → Vue Router 可能捕获到变化         │ │
│   │ ⚡  组件状态            → 可能不一致                         │ │
│   └──────────────────────────────────────────────────────────────┘ │
│                                                                     │
│   window.location.href = '/login' (History 模式)                   │
│   ┌──────────────────────────────────────────────────────────────┐ │
│   │ ✘ 所有导航守卫         → 全部失效                          │ │
│   │ 💥 页面完全刷新         → Vue 应用重启                      │ │
│   │ 💥 应用状态丢失         → Vuex 重置                         │ │
│   │ 💥 内存泄漏风险         → 未清理的事件监听器、定时器        │ │
│   └──────────────────────────────────────────────────────────────┘ │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

六、实战场景深度对比

6.1 场景一:登录拦截

场景:未登录用户访问需要权限的页面

正确做法:使用 $router.push

// router/index.js                                             
  router.beforeEach((to, from, next) => {                        
    const isLoggedIn = store.state.auth.isLoggedIn;              
    if (to.meta.requiresAuth && !isLoggedIn) {                   
      // 保存目标路径,登录后重定向                                
      next({                                                      
        path: '/login',                                           
        query: { redirect: to.fullPath }                          
      });                                                         
    } else {                                                      
      next();                                                     
    }                                                             
  });                                                             
  // Login.vue - 登录成功后                                       
  handleLoginSuccess() {                                          
    const redirect = this.$route.query.redirect || '/dashboard'; 
    this.$router.push(redirect);  // ✔ 正确                     
  }                                                               

错误做法:使用 location.href

// ✘ 错误示例                                                  
  if (!isLoggedIn) {                                             
    window.location.href = '/#/login';                           
    // 问题:                                                     
    // 1. 无法保存 redirect 参数                                  
    // 2. Vuex 状态可能丢失                                       
    // 3. 导航守卫失效,安全检查被绕过                            
  }        

6.2 场景二:Token 过期处理

场景:API 请求返回 401,需要跳转登录页                   

完整解决方案

 // utils/request.js (axios 拦截器)                              
  import router from '@/router';                                 
  import store from '@/store';                                   
  service.interceptors.response.use(                             
    response => response.data,                                   
    error => {                                                    
      if (error.response?.status === 401) {                      
        // 清除用户信息                                           
        store.dispatch('auth/logout');                           
        // ✔ 正确:使用 router 进行跳转                          
        router.push({                                             
          path: '/login',                                         
          query: {                                                
            redirect: router.currentRoute.fullPath,               
            message: '登录已过期,请重新登录'                      
          }                                                       
        });                                                       
        // ✘ 错误示例                                            
        // window.location.href = '/login';                       
        // 会丢失当前的 redirect 信息                             
        // 且 Vuex 状态已清空,但应用可能未正确重置               
      }                                                           
      return Promise.reject(error);                               
    }                                                             
  );     

6.3 场景三:路由懒加载与代码分割

场景:路由懒加载的加载时机                               

路由配置

const routes = [                                               
    {                                                             
      path: '/admin',                                             
      component: () => import('@/views/Admin.vue'),  // 懒加载   
      meta: { requiresAuth: true }                               
    }                                                             
  ];                                                              

$router.push 的懒加载流程

this.$router.push('/admin')              

1. beforeEach 检查权限                   

  • 未登录 → 重定向到登录页            
  • 已登录 → 继续                      

2. 开始加载 Admin.vue 的 chunk          

  • 显示加载指示器                      
  • 网络请求获取 JS 文件                

3. chunk 加载完成                        

  • 执行 beforeEnter                    
  • 创建组件实例                        
  • 渲染页面                            

location.href 的风险

window.location.href = '/admin'                                

问题:

1. 如果是 History 模式 → 页面刷新                               

2. 向服务器请求 /admin 资源                                      

3. 服务器可能返回 404(SPA fallback 未配置)                     

4. 即使返回 index.html,也重新下载了所有资源                     

5. 懒加载的优势被破坏 

七、性能与用户体验对比

7.1 性能对比测试

性能对比(模拟数据)                               

测试场景:从首页跳转到用户中心(包含 3 个 API 请求)               

this.$router.push('/user')                                     

导航开始到页面可交互:                                          

  • 路由守卫执行:     ~5ms                                     
  • 组件实例化:       ~10ms                                    
  • DOM 更新:         ~15ms                                    
  • API 请求:         ~200-500ms                               

总计:             ~230-530ms                               

优势:                                                           

  • 无白屏时间                                                  
  • 可显示加载状态                                              
  • 保持滚动位置(可配置)                                      
  • 资源无需重新下载                                            
window.location.href = '/user' (History 模式)                  

页面刷新流程:                                                  

  • 卸载旧页面:       ~50ms                                    
  • 网络请求 HTML:    ~100-300ms                               
  • 解析 HTML:        ~50ms                                    
  • 下载 JS/CSS:      ~200-500ms                               
  • Vue 初始化:       ~50-100ms                                
  • 组件渲染:         ~30-50ms                                 
  • API 请求:         ~200-500ms                               

总计:             ~680-1550ms                              

劣势:                                                           

  • 明显白屏时间                                                
  • 重复下载资源                                                
  • 丢失应用状态                                                
  • 滚动位置重置                                                                                                                  

性能差距:约 3-5 倍                                               

7.2 用户体验对比

用户体验对比                                    

this.$router.push()                                               
  • 无白屏,过渡流畅                                             
  • 可显示页面加载进度条                                         
  • 支持页面切换动画                                 
  • 保持全局状态(主题、语言等)                                 
  • 可实现页面缓存功能                        
  • 支持 prefetch 预加载                                         
window.location.href                                              
  • 明显的白屏闪烁                                               
  • 浏览器原生加载指示器                                         
  • 无过渡动画                                                   
  • 所有状态丢失,需重新初始化                                   
  • 用户需要重新等待所有资源加载                                 
  • 表单数据、未保存内容丢失                                     

八、常见陷阱与最佳实践

8.1 常见陷阱

常见陷阱汇总                                 

陷阱 1:在 History 模式下硬编码 hash 路径                            

// ✘ 错误:History 模式下跳转到 hash 路径                           
  window.location.href = '/#/login';                                  
  // 结果:URL 变成 https://example.com/#/login                       
  // 与路由配置 /login 不匹配,404!                                  
  // ✔ 正确:使用路由名称或路径                                       
  this.$router.push('/login');                                        
  // 或                                                               
  this.$router.push({ name: 'Login' });                               

陷阱 2:在异步回调中使用 location.href                               

// ✘ 错误示例                                                       
  setTimeout(() => {                                                
    window.location.href = '/dashboard';                              
  }, 1000);                                                           
  // 问题:期间用户可能已经导航到其他页面,造成混乱                    
  // ✔ 正确:使用路由导航                                             
  setTimeout(() => {                                                  
    this.$router.push('/dashboard');                                  
  }, 1000);                                                           
  // 或者更好的做法:在导航守卫中处理                                  

陷阱 3:忽略路由守卫的权限检查                                       

// ✘ 危险:绕过权限检查                                             
  if (userClickedAdminLink) {                                         
    window.location.href = '/admin';                                  
  }                                                                   
  // 即使没有权限,也能访问 admin 页面(虽然 API 会拒绝)              
  // ✔ 正确:通过路由跳转,触发权限检查                               
  if (userClickedAdminLink) {                                         
    this.$router.push('/admin');                                      
    // beforeEach 会检查权限并拒绝导航                                
  }                                                                   

陷阱 4:在新窗口打开链接时误用路由                                   

// ✘ 错误:无法在新窗口打开                                         
  this.$router.push('/help');  // 只能在当前窗口打开                  
  // ✔ 正确:使用原生方式打开新窗口                                   
  const routeData = this.$router.resolve('/help');                    
  window.open(routeData.href, '_blank');                              

8.2 最佳实践

最佳实践指南                                   

1. 统一使用路由导航                                                  

 // 创建导航工具函数                                                   
  // utils/navigation.js                                              
  import router from '@/router';                                      
  export function navigateTo(path, query = {}) {                      
    return router.push({ path, query });                              
  }                                                                   
  export function navigateByName(name, params = {}) {                 
    return router.push({ name, params });                             
  }                                                                   
  export function redirectTo(path) {                                  
    return router.replace(path);                                      
  }                                                                   
  export function openInNewTab(path) {                                
    const routeData = router.resolve(path);                           
    window.open(routeData.href, '_blank');                            
  }                                                                   

2. 合理使用路由守卫                                                  

// 权限守卫                                                          
  router.beforeEach(async (to, from, next) => {                       
    // 白名单路由直接通过                                              
    if (to.meta.whiteList) return next();                             
    // 检查登录状态                                                    
    const isLoggedIn = await checkAuthStatus();                       
    if (!isLoggedIn && to.meta.requiresAuth) {                        
      return next({                                                   
        path: '/login',                                               
        query: { redirect: to.fullPath }                              
      });                                                             
    }                                                                 
    // 权限检查                                                        
    if (to.meta.roles) {                                              
      const userRole = store.state.user.role;                         
      if (!to.meta.roles.includes(userRole)) {                        
        return next('/403');                                          
      }                                                               
    }                                                                 
    next();                                                           
  });                                                                 

3. 何时可以使用 location.href                                        

//  合理使用场景:                                                 
  // 场景 1:跳转到外部网站                                            
  window.location.href = 'https://external-site.com';                 
  // 场景 2:完全重置应用状态(如退出登录)                            
  function logout() {                                                 
    clearAllStorage();                                                
    window.location.href = '/login';  // 完全重置应用                
  }                                                                   
  // 场景 3:非 Vue 管理的页面(如静态页面)                            
  window.location.href = '/static/about.html';                        
  // 场景 4:处理特定错误(如 404 页面在 Vue 之外)                    
  if (isCriticalError) {                                              
    window.location.href = '/error.html';                             
  }                                                                   

九、总结与决策矩阵

9.1 完整对比表

特性this.$router.pushwindow.location.href
页面刷新无刷新可能刷新(History模式)
导航守卫完整触发完全绕过
组件生命周期正常执行可能丢失  
应用状态保持完整可能重置
过渡动画支持不支持
路由懒加载按需加载  可能重复下载
模式兼容性自动适配需手动处理
参数传递query/params手动拼接 URL  
编程控制Promise 返回无返回值
错误处理可捕获错误无错误处理
性能高(无重新加载)低(重新加载)
SEO 友好需 SSR原生支持
推荐程度强烈推荐仅限特殊场景

9.2 选择决策流程图

                    需要在 Vue 应用中跳转页面
                              │
                              ▼
                    ┌─────────────────┐
                    │ 是否为 Vue SPA  │
                    │    内部路由?    │
                    └─────────────────┘
                              │
                 ┌────────────┴────────────┐
                 │                         │
                是                         否
                 │                         │
                 ▼                         ▼
        ┌──────────────┐         ┌─────────────────┐
        │ 使用         │         │ 跳转到外部网站  │
        │ $router.push │         │ 或非 Vue 页面   │
        └──────────────┘         └─────────────────┘
                 │                         │
                 ▼                         ▼
        ┌──────────────────┐      window.location.href
        │ 需要权限检查?   │
        └──────────────────┘
                 │
        ┌────────┴────────┐
        │                 │
       是                 否
        │                 │
        ▼                 ▼
 ┌─────────────┐   ┌─────────────┐
 │ beforeEach  │   │ 直接 push   │
 │ 守卫拦截    │   └─────────────┘
 └─────────────┘
        │
        ▼
 ┌──────────────────────┐
 │ 需要新窗口打开?     │
 └──────────────────────┘
        │
   ┌────┴────┐
   │         │
  是         否
   │         │
   ▼         ▼
┌────────┐ ┌────────┐
│resolve │ │  push  │
│+ open  │ └────────┘
└────────┘

结语

this.$router.push()window.location.href 虽然都能实现页面跳转,但本质上是两种完全不同的架构思路的体现:

  • $router.push() 代表了现代 SPA 应用的设计理念:状态驱动、组件化、声明式,充分利用框架的能力,实现流畅的用户体验和可维护的代码结构。
  • location.href 则是传统多页应用的遗留方式:命令式、页面中心、状态独立,在 SPA 中使用会破坏应用的完整性和用户体验。

核心建议:在 Vue SPA 中,99% 的场景都应该使用 $router.push(),只有在外部跳转、完全重置应用等特殊场景下,才考虑使用 location.href

理解这两者的区别,不仅能帮助你写出更好的代码,更能让你深入理解 SPA 架构的设计哲学。

以上就是深度解析Vue中的路由跳转$router.push VS location.href的详细内容,更多关于Vue路由跳转的资料请关注脚本之家其它相关文章!

相关文章

  • Vue+SpringBoot开发V部落博客管理平台

    Vue+SpringBoot开发V部落博客管理平台

    V部落是一个多用户博客管理平台。这篇文章主要介绍了Vue+SpringBoot开发V部落博客管理平台,需要的朋友可以参考下
    2017-12-12
  • Vue脚手架的简单使用实例

    Vue脚手架的简单使用实例

    这篇文章主要介绍了Vue脚手架的简单使用实例,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-07-07
  • Vue Router路由守卫超详细介绍

    Vue Router路由守卫超详细介绍

    路由守卫,简单理解来说就是,当用户要进行一些操作时,我需要用户的一些信息或数据或行为,我判断过后,才会同意用户进行操作,说到这里,我想大家心里都或多或少有点理解了吧
    2023-01-01
  • vue axios拦截器常用之重复请求取消

    vue axios拦截器常用之重复请求取消

    我们大家在开发中,经常会遇到接口重复请求导致的各种问题,下面这篇文章主要给大家介绍了关于axios拦截器之重复请求取消的相关资料,需要的朋友可以参考下
    2021-09-09
  • 在Vue中使用axios请求拦截的实现方法

    在Vue中使用axios请求拦截的实现方法

    这篇文章主要介绍了在Vue中使用axios请求拦截,需要的朋友可以参考下
    2018-10-10
  • vue中keep-alive组件的入门使用教程

    vue中keep-alive组件的入门使用教程

    这篇文章主要给大家介绍了关于vue中keep-alive组件的入门使用教程,文中通过示例代码介绍的非常详细,对大家学习或者使用vue具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-06-06
  • Vue 数值改变页面没有刷新的问题解决(数据改变视图不更新的问题)

    Vue 数值改变页面没有刷新的问题解决(数据改变视图不更新的问题)

    这篇文章主要介绍了Vue 数值改变页面没有刷新的问题解决(数据改变视图不更新的问题),本文结合实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-09-09
  • vue3+elementPlus二次封装表单的实现代码

    vue3+elementPlus二次封装表单的实现代码

    最近使用Vue3+ElementPlus开发项目,从整体上构思组件的封装。能写成组件的内容都进行封装,方便多个地方使用,这篇文章给大家介绍了vue3+elementPlus二次封装表单的实现,并通过代码介绍的非常详细,需要的朋友可以参考下
    2024-03-03
  • vue如何实现点击选中取消切换

    vue如何实现点击选中取消切换

    这篇文章主要介绍了vue实现点击选中取消切换,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-05-05
  • vue+el-table点击表头实现改变其当前样式

    vue+el-table点击表头实现改变其当前样式

    这篇文章主要介绍了vue+el-table点击表头实现改变其当前样式问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-08-08

最新评论