五、hash模式url如何变化
hash模式下,调用this.$router.push或点击router-link标签,路由的hash会发生相应的变化,当点击浏览器的回退,会回到上一次的hash路径。那么他的跳转是调动了VueRouterclass中的实例方法push,也就是this.history.push(location, onComplete, onAbort),在mode hash下,history的push是hash.js文件中的HashHistory,HashHistory中的push,首先会拿到current,也就是跳转前的路由,然后会执行this.transitionTo,执行成功后导航守卫部分逻辑完成,页面完成渲染,之后首先会调用pushHash(route.fullPath)。pushHash函数首先会判断当前的浏览器环境,设备版本等等,如果不支持window.history.pushState,那么会调用 window.location.hash = path,对hash进行切换。如果支持history.pushState,那么会执行pushState(getUrl(path)),getUrl方法,首先会通过window.location.href拿到当前的路径,然后通过indexOf('#')拿到#的index,目的是获取#之前的baseUrl,最后把传入的path和baseUrl拼接成一个完整的路径返回。也就是说调用pushState传入的是一个最终的完整路径。pushState会判断当前跳转的方式是否是replace,如果是replace那么会最终调用history.replaceState,否则也就是push方法,即history.pushState,这两者,replace会替换histroy的历史栈的当前记录,push则会往历史栈添加指定的url。a->b,当前在b,调用push(c),历史栈为 a->b->c 点击返回会到b,而使用replace,则会把历史栈修改为a->c,点击返回会回到a。如果在Safari浏览器中,某些情况下history堆栈长度会被限制在100,如果出现了问题,那么他会执行,window.location[replace ? 'replace' : 'assign'](url)进行跳转。
// src/util/push-state.js
export function pushState (url?: string, replace?: boolean) {
saveScrollPosition()
// try...catch the pushState call to get around Safari
// DOM Exception 18 where it limits to 100 pushState calls
const history = window.history
try {
if (replace) {
// preserve existing history state as it could be overriden by the user
const stateCopy = extend({}, history.state)
stateCopy.key = getStateKey()
history.replaceState(stateCopy, '', url)
} else {
history.pushState({ key: setStateKey(genStateKey()) }, '', url)
}
} catch (e) {
window.location[replace ? 'replace' : 'assign'](url)
}
}
export function replaceState (url?: string) {
pushState(url, true)
}
那么当点击浏览器的回退按钮,视图也会发生相应的改变,也就是说,vue-router帮助我们监听了这一事件。在根部Vue实例触发beforecreate的时候,会触发init函数,在触发history.transitionTo进行首次跳转之后,会触发回调函数setupListeners,setupListeners函数会执行history.setupListeners()函数,在hash模式下,会对应到HashHistory的setupListeners函数,这个函数首先会通过window.addEventListener添加一个监听事件,监听事件的类型会根据当前浏览器对history的支持度,监听popstate或hashchange事件,当我们点击浏览器的回退按钮的时候,会触发回调函数handleRoutingEvent,handleRoutingEvent函数会通过getHash()拿到要跳转的hash值作为参数,调用transitionTo完成跳转
// src/history/hash.js
export class HashHistory extends History {
...
// this is delayed until the app mounts
// to avoid the hashchange listener being fired too early
setupListeners () {
...
const handleRoutingEvent = () => {
const current = this.current
if (!ensureSlash()) {
return
}
this.transitionTo(getHash(), route => {
if (supportsScroll) {
handleScroll(this.router, route, current, true)
}
if (!supportsPushState) {
replaceHash(route.fullPath)
}
})
}
const eventType = supportsPushState ? 'popstate' : 'hashchange'
window.addEventListener(
eventType,
handleRoutingEvent
)
...
}
}
当我们输入http://localhost:8080的时候,vue-router会帮助我们自动加上/#/。在classHashHistory初始化的时候会执行ensureSlash函数,ensureSlash函数首先会通过getHash方法拿到hash,如果我们输入的是没有#符号的url,那么会return一个空的字符串,之后会走replaceHash('/' + path)方法,replaceHash在调用replaceState或 window.location.replace,会把getUrl(path)作为参数,getUrl会把传入的path和base做一个拼接${base}#${path},所以会帮助我们拼接出一个带有#号的初始url,另外这部分用的是replaceState或window.location.replace这也是为了防止把我们输入的不带#号的url作为历史栈
// src/history/hash.js
export class HashHistory extends History {
constructor (router: Router, base: ?string, fallback: boolean) {
super(router, base)
// check history fallback deeplinking
if (fallback && checkFallback(this.base)) {
return
}
ensureSlash()
}
...
ensureURL (push?: boolean) {
const current = this.current.fullPath
if (getHash() !== current) {
push ? pushHash(current) : replaceHash(current)
}
}
...
}
...
function ensureSlash (): boolean {
const path = getHash()
if (path.charAt(0) === '/') {
return true
}
replaceHash('/' + path)
return false
}
export function getHash (): string {
// We can't use window.location.hash here because it's not
// consistent across browsers - Firefox will pre-decode it!
let href = window.location.href
const index = href.indexOf('#')
// empty path
if (index < 0) return ''
href = href.slice(index + 1)
return href
}
function getUrl (path) {
const href = window.location.href
const i = href.indexOf('#')
const base = i >= 0 ? href.slice(0, i) : href
return `${base}#${path}`
}
function pushHash (path) {
if (supportsPushState) {
pushState(getUrl(path))
} else {
window.location.hash = path
}
}
function replaceHash (path) {
if (supportsPushState) {
replaceState(getUrl(path))
} else {
window.location.replace(getUrl(path))
}
}