背景介绍
前几天面试官问我能不能讲一下 vue-router 两种模式,好家伙不就是 #/path
和 /path
这还用问?面试官浅笑一下,呵呵,讲得有点表面,深层的地方没有讲出来,好家伙,vue-router 算是埋伏了我一手,所以这篇文章将会带你把 vue-router 这两种模式的底裤扒出来看看
有什么用?
前面说过,我印象最深的就是 vue-router 的 Hash 模式,它在切换的时候根目录后面是会跟着一个 #
字符,那么为什么选中了它呢?
首先讲一下 # 的含义
- 代表位置
#
可以作为一个锚点标记元素的位置,和 <a>
元素搭配可以起到跳转的作用
<a href="#xxx">跳转到到 xxx 位置</a>
<!-- ....假设中间有很多元素.... -->
<div id="xxx"></div>
这个作用是不是很符合路由或者说我们平时点击网页时那种导航的作用呢?
- 浏览器 url '不认' #
#
不是浏览器 url 的亲儿子
- 发送 http 请求时,# 及其后面的字符 '不认',不包含在请求参数,也不算请求 URL,不会发送到服务器
- 在浏览器中当前页面的 URL 输入栏中,随意打上
#
+ 任意字符并回车,不会触发重载页面,因为浏览器会认为你想要跳转到当前页面某个位置 - google spider(谷歌的爬虫)会忽视掉 # 后面的内容,也就是采取这种方式的内容型网站的路由对应的页面内容不会被谷歌的浏览引擎检索到并推荐到搜索内容中(你嫌爬不到我还不想给呢),这也就是为什么 vue-router 4 中说 Hash 模式对 SEO 有不好的影响
那么 # 在 vue-router 中是干什么的?
前面主要说了 # 可以用来标记锚点元素,而浏览器也提供了 onhashchange 的事件,可以帮助我们监听路由的变化,从而和浏览器的前进后退动作联系在一起,这里的实现先不讲,在后面中与 history 的区别中会详细介绍
所以 vue-router 的 hash 模式的 #
字符并不是空穴来风,而是源于浏览器提供的原生 API
hash 和 history 两种模式的区别
既然是详解,那肯定的扒源码来看看,vue-router 选择不同的模式的时候是怎么处理的呢?(以 vue-router 3 和 4 版本比较)
首先是 vue-router3
export default class VueRouter {
...
constructor (options: RouterOptions = {}) {
...
switch (mode) {
case 'history':
this.history = new HTML5History(this, options.base)
break
case 'hash':
this.history = new HashHistory(this, options.base, this.fallback)
break
case 'abstract':
this.history = new AbstractHistory(this, options.base)
break
default:
if (process.env.NODE_ENV !== 'production') {
assert(false, `invalid mode: ${mode}`)
}
}
}
...
}
可以看到,history 模式和 hash 模式是和 HTML5History 和 HashHistory 两个对象相关的,HTML5History 用的是 history 提供的 pushState 和 replaceState 两个方法,HashHistory 用的是 hashchange 事件监听,你们可以猜一下 vue-router4 的 hash 模式是怎么实现的
// 摊牌了,我不装了,其实我是 history 模式套壳
import { createWebHistory } from './html5'
...
export function createWebHashHistory(base?: string): RouterHistory {
...
return createWebHistory(base)
}
鉴于 vue-router4 对于 hash 模式直接开摆,因此只会浅尝一下 vue-router3 的源码
无论是 hash 还是 history 模式,都需要实现路由的功能,即下面两个核心
- 改变 URL 却不引起刷新
检测 URL 的变化, URL 变化的方式有以下几种
- 浏览器前进后退
- a 标签超链接改变
- window.location 改变
改变 URL 但不会引起刷新
hash
对于第一个,前面说过 hash 模式采用 #
+ path 的方式,改变了 URL 但是浏览器不会刷新重载
在代码层面就是 window.location.hash 的修改
// vue-router/src/history/hash.js 139
function pushHash(path) {
if (supportsPushState) {
pushState(getUrl(path));
} else {
window.location.hash = path;
}
}
history
调用 history 中的 pushState 方法以及 replaceState 方法,也不会引起页面刷新
history.pushState(state, title[, url])
具体参数详情参考 MDN 文档的介绍,友情链接
// 跳转到登录页
history.pushState(null, "", "login");
检测 URL 的变化
hash
hash 监听的是 hashchange 事件,在 vue-router/src/history/hash.js 49 行的 setupListeners 方法中,设置了监听器
export class HashHistory extends History {
...
setupListeners () {
const eventType = supportsPushState ? 'popstate' : 'hashchange'
window.addEventListener(
eventType,
handleRoutingEvent
)
...
}
}
history
不用说你都应该知道,就是上面源码中的 popstate 事件
因此在检测 URL 的变化的策略上,hash 是监听的 hashchange 事件,而 history 监听的是 popstate 事件
改变 URL 的方法
window.location
hash 会修改 window.location.hash 来达到修改 URL 的目的,而 history 不会
a 标签
<router-link>
里面其实就是 <a>
(默认的情况下),hash 模式下,直接点击跳转就行了,因为有 # 锚点标记,但是 history 要阻止跳转,不然就会想服务器发送请求了,拦截后,调用 pushState/replaceState
浏览器前进后退
浏览器前进后退其实 history.go,back,forward 的调用,两种模式都是可以正常使用的,因为无论是 hash 还是 history 都会向历史记录栈中存入记录
总结
看了 vue-router 中关于 hash 和 history 部分,基本就可以了解 vue-router 的运作原理了,对于 hash 和 history,hash 是 2014 年前采用的方法,history 是 HTML5 的新 API,以后应该都是采用 history 的比较多,这一点可以参考 vue-router4 的 hash 模式,直接开摆了属于是