vue-router源码解析系列。这是第九篇。本篇介绍源码中的hash.js
,它其实比较简单,是vue-router
在mode:hash
模式下的History
子类实现,Histor
类是路由跳转的核心类,在之前的博客中已有详细的解析。本系列解析的是官方git库中3.1.6的版本源码。
源码链接:hash.js。源码里面用的是typescript,但是不影响阅读。
1 | export class HashHistory extends History { |
为什么还是优先使用history而不是纯hash
这是因为hash模式,本身就是可以用history来实现的,不一定是hashchange
这种比较旧的方式,通常选择hash模式,只是单纯地为了让应用能够兼容更早的浏览器版本,那如果一个浏览器已经完成支持history pushState relaceState
和popstate
事件了,那用最新的api去实现hash模式,显然更合适。
为什么能这么做呢?最重要的是为什么能用popstate
事件取代hashchange
事件呢,难道改变hash的时候除了触发hashchange
,还会触发popstate
事件吗?
确实如此,看了自己一篇16年的旧博客,发现一些对hashchange popstate
事件有用的知识点:理解浏览器历史记录(2)-hashchange、pushState
window.onpopstate事件
这个事件触发的时机比较有特点:
一、history.pushState和history.replaceState都不会触发这个事件
二、仅在浏览器前进后退操作、history.go/back/forward调用、hashchange的时候触发
这就明白为啥HashHistory
里面在supportsPushState
为真的情况下,可以用popstate
事件代替hashchange
事件的原因了。
构造函数中的要点
在构造函数中,有一段:1
2
3if (fallback && checkFallback(this.base)) {
return
}
首先fallback
是Router
类中传进来的:1
2
3
4
5
6
7
8
9
10
11
12
13
14// 以下代码有简化
let mode = options.mode || 'hash'
this.fallback = mode === 'history' && !supportsPushState && options.fallback !== false
// 向后兼容
if (this.fallback) {
mode = 'hash'
}
switch (mode) {
case 'hash':
this.history = new HashHistory(this, options.base, this.fallback)
break
}
fallback
代表了兼容的意思,那就意味着有可能当前的访问地址还是非hash的模式,所以在HashHistory
的构造函数中,在检测到fallback
的情况下加了一个checkFallback
的处理,这个会将当前非hash的访问地址,变为hash模式的访问地址。比如一开始访问的是http://localhost:8080/
会被修改为http://localhost:8080/#/
。
构造函数中还有1个this.ensureSlash
的调用,这个也是修正访问地址的作用,比如你访问的是http://localhost:8080/#list
,则会被换成http://localhost:8080/#/list
,然后才去执行初始化路由跳转,否则跟http://localhost:8080/#list
去访问,是匹配不到路由的。
pushHash和replaceHash会导致hashchange
被触发吗
经过测试supportsPushState
为true
的情况下,那么pushHash
和replaceHash
最终是通过history.pushState
和history.replaceState
完成的hash更新,这两个api不会触发hashchange
事件,所以popstate
事件也不会触发。
但是在supportsPushState
为false
的情况下,就不一样了:pushHash
和replaceHash
最终是通过location.hash赋值
和location.replace
完成的hash更新,这两个方法完成的hash更新。 那么问题来了?如果代码中通过this.push
和this.replace
触发路由,那么由于hashchange
也会执行,所以会导致listener中的this.transitionTo
也会执行,会有问题吗?
不会。最终即使进入了回调,执行到this.transitionTo的调用,最终也会因为NavigationDuplicated取消掉由于hashchange事件触发的路由。
setupListeners
1 | window.addEventListener( |
首先是这个this.ensureSlash
的调用,它不单是返回true false
,里面还有replaceHash
的处理,如果手动修改hash
为一个非/
开始的字符串,就会发现this.ensureSlash
返回false
,路由中止,同时浏览器访问地址中的hash会自动以/
开头。如果它this.ensureSlash
不返回false
,而是在保证了hash的/
开头的逻辑后,继续走this.transitionTo
的处理,我感觉更有用一点。现在直接return
了,就看不出作用了。
这个ensureSlash
也有别人对它的疑问:参见issue
然后是这个调用:1
2
3if (!supportsPushState) {
replaceHash(route.fullPath)
}
为啥在纯hash模式下,最后还有加replaceHash
的调用呢?因为在每个路由成功后,都有this.ensureURL
的处理阿,个人认为它是多余的。