前端vue-router路由原理解析及常见面试题

简介: 前端vue-router路由原理解析及常见面试题

1. 前端路由 router 原理及表现

  • 核心都是改变url,但不刷新页面,不向服务器发送请求

1.1 hash路由

  • url 的 hash 是以 # 开头,当 hash 改变时,页面不会因此刷新,浏览器也不会向服务器发送请求。

    • 特点:兼容性好、丑陋、对于后端路由来说不区分#号后面的内容
    • http://a.com/web#order
    • http://a.com/web#goods
    • 以上两个路由对于后端来说没有区别,都是/web路径下的,所以不需要后端特殊支持
  • 更改hash及hashchange事件

    location.hash = '#/news' 
    // https://www.baidu.com  -> https://www.baidu.com/#news
    location.replace('#/detial') // 替换当前记录
    // 同时在浏览器生成一条记录,点击回退按钮会回到原url
    
    // 监听hash的变化,显示不同的内容
    window.addEventListener('hashchange', function () {
      // 隐藏掉之前路由的内容
      // 显示当前路由的内容
      // document.querySelector('#root').innerHTML = '当前hash内容'
      console.log('render');
    });
  • hash 路由例子

      <div>
        <div>
          <h1>hash 路由</h1>
          <a href="#/list">列表页</a>
          <a href="#/detail">详情页</a>
          <a href="#/other">404</a>
        </div>
        <div id="app" style="border: 1px solid black; min-height: 200px;"></div>
      </div>
    <script>
      // 定义路由映射表
      var routerObj = {
        '#/list': '<div>列表页</div>',
        '#/detail': '<div>详情页</div>'
      }
    
      window.addEventListener('hashchange', function() {
        // 监听hash路由变化
        // 拿到映射对应的组件进行渲染
        document.getElementById('app').innerHTML = routerObj[location.hash] || '404页面'
      })
    
    </script>

1.2 history路由

  • HTML5 规范中提供了 history.pushState 和 history.replaceState 来进行路由控制。通过这两个方法,可以实现改变 url 且不向服务器发送请求
  • history api demo

    history.pushState({}, '', '/news') 
    // https://www.baidu.com -> https://www.baidu.com/news
    // 同时在浏览器生成一条记录,点击回退按钮会回到原url(repalceState会覆盖掉当前的记录)
  • 需要服务端配合,避免刷新404

  • history路由没有hash路由类似的hashchange事件。
  • 改变当前url有两种方式

    1. 点击后退/前进 -> popstate事件
    2. history.pushState history.replaceState -> 触发相应的函数后,在后面手动添加回调
  • history 路由 demo

      <div id="history-box">
        <h1>history 路由</h1>
        <a href="/web/list">列表页</a>
        <a href="/web/detail">详情页</a>
        <a href="/web/other">404</a>
      </div>
    
      <script>
        // history 路由demo
        var routerHistoryObj = {
          '/web/list': '<div>history 列表页</div>',
          '/web/detail': '<div>history 详情页</div>'
        }
    
        // 为每个链接添加点击事件
        var length = document.querySelectorAll('#history-box a[href]').length
        for(var i = 0; i < length; i++) {
          document.querySelectorAll('#history-box a[href]')[i].addEventListener('click', function(event) {
            event.preventDefault();
            window.history.pushState({}, null, event.currentTarget.getAttribute('href') );
            handleHref();
          })
        }
  // 监听前进/后退 引起的posstate事件
  window.addEventListener('popstate', handleHref);

  // 根据新的路由,显示新的组件
  function handleHref () {
    document.getElementById('app').innerHTML = routerHistoryObj[location.pathname] || '404页面'
  }

</script>

// nodejs路由处理 /web -> /web*
app.get('/web*', (req, res) => {

res.sendFile(__dirname + '/index.html');

})


# 2.vue.js router 使用详解

## 2.1 路由应用示例

![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/25f0fd9aaad045af9b57e0a6683746c4~tplv-k3u1fbpfcp-watermark.image?)

## 2.2 引入vue-router并注册插件

Vue.use(VueRouter)
// 引入了两个组件 router-link和router-view,及全局混入了$route $router


### 2.2.1 router-link 用法
* to 字符串 | Location对象
  * 字符串,手动拼接的
  * {name: '', query: {}, params: {}}
  * {path: '', query: {}, params: {}}
* tag 默认为a
* repalce
* 与手写a链接的区别,router-link抹平了两种模式下href的书写方式,会得到正确的href值;history模式下调用pushState并阻止默认行为。


### 2.2.2 router-view
* 确定路由组件显示的位置
* 可以嵌套
* 命名router-view



const router = new VueRouter({
routes: [

{
  path: '/',
  components: {
    default: Foo,
    a: Bar,
    b: Baz
  }
}

]
})


### 2.2.3 this.$route 
* params
* query
* matched // 匹配的路由记录
* path

### 2.2.4 this.$router
* push(location)
* replace(location)
* go(n)
* back()
* forward()
* resolve() 
* const {href} = this.$router.resolve(location) // 得到完整的url,可以window.open打开

## 简单的routes和component demo

// 0. 注册插件 Vue.use(VueRouter)
// 1. 定义 (路由) 组件。
// 可以从其他文件 import 进来
const Foo = { template: '

foo
' }
const Bar = { template: '
bar
' }

// 2. 定义路由
// 每个路由应该映射一个组件。
const routes = [
{ path: '/foo', component: Foo },
{ path: '/bar', component: Bar }
]

// 3. 创建 router 实例,然后传 routes 配置

const router = new VueRouter({
routes // (缩写) 相当于 routes: routes
})

// 4. 创建和挂载根实例。
// 记得要通过 router 配置参数注入路由,
// 从而让整个应用都有路由功能
const app = new Vue({
router
}).$mount('#app')


## 命名路由
* 可以直接通过名字跳转,后续如果更改了path,则不影响name的跳转
* 设置了默认的子路由,则父路由的name会被警告⚠️,通过name跳转父路由则不会显示默认的子路由

## 子路由
* 默认子路由: path: ''
* 子路由中的path是否以'/'开头的区别,加'/'是绝对路径,不加是相对

children: [

  {
      path: '',
      component: NewsAdd,
      name: 'newsDefault'
  },
  {
      path: 'add',
      component: NewsAdd,
      name: 'newsAdd'
  },
  {
      path: 'detail/:id',
      component: NewsDetail,
      name: 'newsDetail',
  }

]

## 动态匹配路由
params: `/user/:username`

### 响应路由参数变化
* watch
watch: {
  '$route.params.id'() {
      this.getNews()
  }
},

* beforeRouteUpdate v2.2
beforeRouteUpdate(to, from, next) {
  this.getNews(to.params.id)
  next()
},

## 404路由

// 含有通配符的路由应该放在最后
{

  path: '*',
  component: NotFound,

},



## 导航守卫

### 全局守卫
* 前置守卫: beforeEach(to, from, next)
  * 必须调用next()才可继续
  * next('/')  next({path: '/'}) 当前的导航被中断,然后进行一个新的导航。比如访问需要登录的页面,如果没有登录的话, 就跳转到登录页
* 解析守卫: beforeResolve(to, from, next)
  * 2.5.0新增
  * 组件内守卫和异步路由组件被解析之后,导航被确认之前被调用
* 后置守卫: afterEach(to, from)
  * 无next参数,不会改变导航,因为导航已被确认
  
### 路由独享守卫
* beforeEnter

const router = new VueRouter({

routes: [
    {
    path: '/foo',
    component: Foo,
    beforeEnter: (to, from, next) => {
        // ...
    }
    }
]

})

### 组件守卫
* beforeRouteEnter(to, from, next)
* 在渲染该组件的对应路由被 confirm 前调用
* 不能访问this,组件实例还未被创建
* 可以给next传递一个回调访问this,也是唯一一个支持给next传递回调的守卫

beforeRouteEnter (to, from, next) {

next(vm => {
    // 通过 `vm` 访问组件实例
})

}

* beforeRouteUpdate(to, from, next)
* 在当前路由改变,但是该组件被复用时调用
* 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。

beforeRouteUpdate (to, from, next) {

// just use `this`
this.name = to.params.name
next()

}

* beforeRouteLeave(to, from, next)
* 导航离开该组件的对应路由时调用
* 这个离开守卫通常用来禁止用户在还未保存修改前突然离开。该导航可以通过 next(false) 来取消。

beforeRouteLeave (to, from, next) {

const answer = window.confirm('您确定离开吗?还有未保存的更改')
if (answer) {
    next()
} else {
    next(false)
}

}


## 路由元信息meta
* 比如在路由鉴权中使用
* $route.matched来检查路由中的字段,因为路由是可以嵌套的,一个路由匹配成功后,可能会匹配多个路由记录

router.beforeEach((to, from, next) => {

if (to.matched.some(record => record.meta.requiresAuth)) {
  // this route requires auth, check if logged in
  // if not, redirect to login page.
  if (!auth.loggedIn()) {
    next({
      path: '/login',
      query: { redirect: to.fullPath }
    })
  } else {
    next()
  }
} else {
  next() // 确保一定要调用 next()
}

})



# 路由懒加载及异步组件
* 优点:在这个组件需要被渲染的时候才会触发该工厂函数
* 路由懒加载,异步组件需要一个函数

const Foo = () => import('./Foo.vue')
// / webpackChunkName: "group-foo" /
const router = new VueRouter({

routes: [
    { path: '/foo', component: Foo }
]

})

* 异步组件

new Vue({

// ...
components: {
    'my-component': () => import('./my-async-component')
}

})

* vue-cli3默认支持。在webpack中需要使用 **syntax-dynamic-import** 插件,才能使babel支持
* prefetch: vue-cli3 对动态 import() 生成的资源 自动添加prefetch,当前页面可能会用到的资源,在浏览器**空闲时加载**
* preload: vue-cli3 应用会为所有初始化渲染需要的文件自动生成preload,用来指定页面加载后很快会被用到的资源(资源预加载)

# 常见面试题
## 如何重定向页面

const router = new VueRouter({

routes: [
    { path: '/a', redirect: '/b' }
]

})


## 路由有几种模式?说说它们的区别?
[参考上一篇文章](https://juejin.cn/post/7106685401095471134#heading-1)

  1. hash模式、2. history模式
  
  如果使用history模式,服务端需要将页面请求重定向到index路径:
    // 例如 Nginx 配置
    location / {
        try_files $uri $uri/ /index.html;
    }
```

讲一下完整的导航守卫流程?

1. 导航被触发。
2. 在失活的组件里调用离开守卫。
3. 调用全局的 `beforeEach` 守卫。
4. 在`重用的组件`里调用 `beforeRouteUpdate` 守卫(2. 2+)。
5. 在路由配置里调用 `beforeEnter`。
6. 解析异步路由组件。
7. 在被激活的组件里面调用 `beforeRouterEnter`。
8. 调用全局的 `beforeResolve` 守卫(2. 5+)。
9. 导航被确认。
10. 调用全局的 `afterEach`钩子。
11. 触发 `DOM` 更新。
12. 用创建好的实例调用 `beforeRouteEnter` 守卫中传给 `next` 的回调函数。

路由导航守卫和Vue实例生命周期钩子函数的执行顺序?

1.  `beforeRouteLeave`:路由组件的组件离开路由前钩子,可取消路由离开。
2.  `beforeEach`: 路由全局前置守卫,可用于登录验证、全局路由loading等。
3.  `beforeEnter`: 路由独享守卫
4.  `beforeRouteEnter`: 路由组件的组件进入路由前钩子。
5.  `beforeResolve`:[路由全局解析守卫](https://link.segmentfault.com/?enc=U0iz6GLW5SX5n2SVFiv1Hg%3D%3D.%2B1Qyyr40B9Ah6%2B9TEBowGKk6LP6kOh0B4el%2Fdi0veWuk210VOgHCM2JYZab56BKCDN9834PLc5gO2%2F13vQc1EoSL4G%2Fziwwg%2B8v9ApfEOn3Wrs7CdnmGyyCKURQaUy%2FKqI3mieLYMFEoCH6A0smmXPLHdkzbCC3JzDD4h7Dny1g%3D)
6.  `afterEach`:路由全局后置钩子
7.  `beforeCreate`:组件生命周期,不能访问`this`。
8.  `created`:组件生命周期,可以访问`this`,不能访问dom。
9.  `beforeMount`:组件生命周期
10.  `deactivated`: 离开缓存组件a,或者触发a的`beforeDestroy`和`destroyed`组件销毁钩子。
11.  `mounted`:访问/操作dom。
12.  `activated`:进入缓存组件,进入a的嵌套子组件(如果有的话)。
13.  执行beforeRouteEnter回调函数next。

路由组件和路由为什么解耦,怎么解耦?

  1. 解耦前

    const Home = {
      template: '<div>User {{ $route.params.id }}</div>'
    }
    const router = new VueRouter({
        routes: [
            { path: '/home/:id', component: Home }
        ]
    })
    1. 使用props解耦后,props为true,route.params将会被设置为组件属性。

      const Home = {
        props: ['id'],
        template: '<div>User {{ id }}</div>'
      }
      const router = new VueRouter({
          routes: [
              { path: '/home/:id', component: Home, props: true},
          ]
      })

直接使用a链接与使用router-link的区别

区别是页面会不会重新渲染

Vue路由怎么跳转打开新窗口?

const obj = {
    path: xxx,//路由地址
    query: {
      mid: data.id//可以带参数
    }
};
const {href} = this.$router.resolve(obj);
window.open(href, '_blank');

如何将vue-router选项进行扁平化处理

[
  {
    path: '/',
    component: Index,
  },
  {
    path: '/news',
    component: News,
    children: [
      {
        path: 'detial',
        component: Detail,
        children: [....]
      }
    ]
  },
}
本质是对树结构的扁平化处理,可以使用BFS(广度优先搜索)或DFS(深度优先搜索)
{
  '/': {path, component, ...},
  '/news': ...,
  '/news/detail': ...,
}
相关文章
|
4月前
|
人工智能 JavaScript 算法
Vue 中 key 属性的深入解析:改变 key 导致组件销毁与重建
Vue 中 key 属性的深入解析:改变 key 导致组件销毁与重建
551 0
|
5月前
|
JavaScript 数据可视化 前端开发
基于 Vue 与 D3 的可拖拽拓扑图技术方案及应用案例解析
本文介绍了基于Vue和D3实现可拖拽拓扑图的技术方案与应用实例。通过Vue构建用户界面和交互逻辑,结合D3强大的数据可视化能力,实现了力导向布局、节点拖拽、交互事件等功能。文章详细讲解了数据模型设计、拖拽功能实现、组件封装及高级扩展(如节点类型定制、连接样式优化等),并提供了性能优化方案以应对大数据量场景。最终,展示了基础网络拓扑、实时更新拓扑等应用实例,为开发者提供了一套完整的实现思路和实践经验。
544 77
|
8月前
|
安全 算法 网络协议
解析:HTTPS通过SSL/TLS证书加密的原理与逻辑
HTTPS通过SSL/TLS证书加密,结合对称与非对称加密及数字证书验证实现安全通信。首先,服务器发送含公钥的数字证书,客户端验证其合法性后生成随机数并用公钥加密发送给服务器,双方据此生成相同的对称密钥。后续通信使用对称加密确保高效性和安全性。同时,数字证书验证服务器身份,防止中间人攻击;哈希算法和数字签名确保数据完整性,防止篡改。整个流程保障了身份认证、数据加密和完整性保护。
|
4月前
|
JavaScript 前端开发 UED
Vue 手风琴实现的三种常用方式及长尾关键词解析
手风琴效果是Vue开发中常见的交互组件,可节省页面空间、提升用户体验。本文介绍三种实现方式:1) 原生Vue结合数据绑定与CSS动画;2) 使用Element UI等组件库快速构建;3) 自定义指令操作DOM实现独特效果。每种方式适用于不同场景,可根据项目需求选择。示例包括产品特性页、后台菜单及FAQ展示,灵活满足多样需求。附代码示例与资源链接,助你高效实现手风琴功能。
164 10
|
4月前
|
JavaScript 前端开发 UED
Vue 表情包输入组件实现代码及详细开发流程解析
这是一篇关于 Vue 表情包输入组件的使用方法与封装指南的文章。通过安装依赖、全局注册和局部使用,可以快速集成表情包功能到 Vue 项目中。文章还详细介绍了组件的封装实现、高级配置(如自定义表情列表、主题定制、动画效果和懒加载)以及完整集成示例。开发者可根据需求扩展功能,例如 GIF 搜索或自定义表情上传,提升用户体验。资源链接提供进一步学习材料。
211 1
|
7月前
|
机器学习/深度学习 数据可视化 PyTorch
深入解析图神经网络注意力机制:数学原理与可视化实现
本文深入解析了图神经网络(GNNs)中自注意力机制的内部运作原理,通过可视化和数学推导揭示其工作机制。文章采用“位置-转移图”概念框架,并使用NumPy实现代码示例,逐步拆解自注意力层的计算过程。文中详细展示了从节点特征矩阵、邻接矩阵到生成注意力权重的具体步骤,并通过四个类(GAL1至GAL4)模拟了整个计算流程。最终,结合实际PyTorch Geometric库中的代码,对比分析了核心逻辑,为理解GNN自注意力机制提供了清晰的学习路径。
489 7
深入解析图神经网络注意力机制:数学原理与可视化实现
|
7月前
|
机器学习/深度学习 缓存 自然语言处理
深入解析Tiktokenizer:大语言模型中核心分词技术的原理与架构
Tiktokenizer 是一款现代分词工具,旨在高效、智能地将文本转换为机器可处理的离散单元(token)。它不仅超越了传统的空格分割和正则表达式匹配方法,还结合了上下文感知能力,适应复杂语言结构。Tiktokenizer 的核心特性包括自适应 token 分割、高效编码能力和出色的可扩展性,使其适用于从聊天机器人到大规模文本分析等多种应用场景。通过模块化设计,Tiktokenizer 确保了代码的可重用性和维护性,并在分词精度、处理效率和灵活性方面表现出色。此外,它支持多语言处理、表情符号识别和领域特定文本处理,能够应对各种复杂的文本输入需求。
840 6
深入解析Tiktokenizer:大语言模型中核心分词技术的原理与架构
|
7月前
|
传感器 人工智能 监控
反向寻车系统怎么做?基本原理与系统组成解析
本文通过反向寻车系统的核心组成部分与技术分析,阐述反向寻车系统的工作原理,适用于适用于商场停车场、医院停车场及火车站停车场等。如需获取智慧停车场反向寻车技术方案前往文章最下方获取,如有项目合作及技术交流欢迎私信作者。
454 2
|
7月前
|
负载均衡 JavaScript 前端开发
分片上传技术全解析:原理、优势与应用(含简单实现源码)
分片上传通过将大文件分割成多个小的片段或块,然后并行或顺序地上传这些片段,从而提高上传效率和可靠性,特别适用于大文件的上传场景,尤其是在网络环境不佳时,分片上传能有效提高上传体验。 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~