<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>SPA</title>
</head>
<body>
<div id="rotes">
<a href="index">index</a>
<a href="login">login</a>
</div>
<div id="routerContent"></div>
</body>
<script type="text/javascript">
class Roter{
constructor(){
this.router={};
this.nowUrl=null;
}
renter(path,callback){
this.router[path] = (type)=>{
if(type==1){
history.replaceState({path},null,path);
}else if(type==2){
history.pushState({path},null,path);
}
callback();
}
}
refresh(type){
this.router[this.nowUrl] && this.router[this.nowUrl](type);
}
init(){
window.addEventListener("load",(e)=>{
this.nowUrl = location.href.slice(location.href.indexOf("/",9))
})
window.addEventListener('popstate', (e) => {
this.nowUrl = history.state.path;
this.refresh(1)
});
document.querySelectorAll('#rotes a').forEach((el)=>{
el.addEventListener('click',(e)=>{
e.preventDefault();
let href = e.target.href.slice(e.target.href.indexOf("/",9));
this.nowUrl = href;
this.refresh(2);
})
})
}
}
let router = new Roter();
router.init()
router.renter('/index',()=>[
routerContent.innerHTML="index"
])
router.renter('/login',()=>[
routerContent.innerHTML="login"
])
</script>
</html>
往返缓存
默认情况下,浏览器会缓存当前会话页面,这样当下一个页面点击后退按钮,或前一个页面点击前进按钮,浏览器便会从缓存中提取并加载此页面,这个特性被称为“往返缓存”。
此缓存会保留页面数据、DOM和js状态,实际上是将整个页面完好无缺地保留
往历史记录栈中添加记录:pushState(state, title, url)
浏览器支持度: IE10+
- state: 一个 JS 对象(不大于640kB),主要用于在 popstate 事件中作为参数被获取。如果不需要这个对象,此处可以填null
- title: 新页面的标题,部分浏览器(比如 Firefox )忽略此参数,因此一般为 null
- url: 新历史记录的地址,可为页面地址,也可为一个锚点值,新 url 必须与当前 url处于同一个域,否则将抛出异常,此参数若没有特别标注,会被设为当前文档 url
replaceState(state, title, url)
改变当前的历史记录
history.state
返回当前历史记录的 state
popstate
- 定义:每当同一个文档的浏览历史(即 history 对象)出现变化时,就会触发 popstate 事件。
- 注意:若仅仅调用 pushState 方法或 replaceState 方法,并不会触发该事件,只有用户点击浏览器倒退按钮和前进按钮,或者使用 JavaScript 调用 back 、 forward 、 go方法时才会触发。另外,该事件只针对同一个文档,如果浏览历史的切换,导致加载不同的文档,该事件也不会触发。
总结
一般场景下,hash 和 history 都可以,除非你更在意颜值,# 符号夹杂在 URL 里看起来确实有些不太美丽。
另外,根据 Mozilla Develop Network 的介绍,调用 history.pushState() 相比于直接修改 hash,存在以下优势:
- pushState() 设置的新 URL 可以是与当前 URL 同源的任意 URL;而 hash 只可修改 #后面的部分,因此只能设置与当前 URL 同文档的 URL
- pushState() 设置的新 URL 可以与当前 URL 一模一样,这样也会把记录添加到栈中;而 hash设置的新值必须与原来不一样才会触发动作将记录添加到栈中
- pushState() 通过 stateObject 参数可以添加任意类型的数据到记录中;而 hash 只可添加短字符串;
- pushState() 可额外设置 title 属性供后续使用。
这么一看 history 模式充满了 happy,感觉完全可以替代 hash 模式,但其实 history 也不是样样都好,虽然在浏览器里游刃有余,但真要通过 URL 向后端发起 HTTP 请求时,两者的差异就来了。尤其在用户手动输入 URL 后回车,或者刷新(重启)浏览器的时候。
- hash 模式下,仅 hash 符号之前的内容会被包含在请求中,如http://www.qq.com,因此对于后端来说,即使没有做到对路由的全覆盖,也不会返回 404 错误。
- history 模式下,前端的 URL 必须和实际向后端发起请求的 URL 一致,如http://www.qq.com/book/id。如果后端缺少对 /book/id 的路由处理,将返回 404错误。Vue-Router官网里如此描述:“不过这种模式要玩好,还需要后台配置支持……所以呢,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。”
- 需在后端(Apache 或 Nginx)进行简单的路由配置,同时搭配前端路由的 404 页面支持。
very good