历史小剧场
唐代实在太高太强了,他们忽忘了民族界限,他们不懂害怕外国人,不懂提防外国人,大量使用外国人当兵作将,结果才弄得不可收拾。于是唐代的府兵一变而成为“藩镇”,军阀割据,胡族临制。----《中国历代政治得失》
location 和 history接口
window.location
说到location对象,它有很多的属性。我们可以通过改变其属性值修改页面的url。我们在单页应用中需要做到的是改变url不刷新页面,location接口提供以下两种方式可以做到:
- locaiton.href 赋值时只改变url的hash
- 直接赋值location.hash
除此之后,还有一个常用方法:location.search(): 会直接刷新页面。
window.history
history 接口 是 HTML5 新增的,它有5个方法可以改变url而不刷新页面。
- history.pushSate()
- history.replaceState()
- history.forward(). 等价于 history.go(1): 页面前进
- history.back() 等价于 history.go(-1): 页面后退
如何监听url的变化
hashchange
hashchange 事件能监听 url hash 的改变
window.addEventListener('hashchange', function(e) {console.log(e)
})
popstate
popstate 事件能监听history.forward() 和 history.back() 方法导致url的变化
window.addEventListener('popstate', (e) => {console.log("state => ", e.state.page)loadHTML(e.state.page, content)
})
hash模式和history模式
前端路由
前端路由是后来发展到SPA(但页面应用)时才出现的概念。SPA就是一个WEB项目只有一个HTML页面,一旦页面加载完成,SPA不会因为用户的操作而进行页面的重新加载或跳转。
优点:前后端的彻底分离,不刷新页面,用户体验较好,页面持久性较好。
hash模式
- 把前端的路由用#拼接到真实url后面的模式,但是会覆盖锚点定位元素的功能,通过监听URL的哈希部分变化,响应地更新页面的内容
- 前端路由的处理完全在客户端进行,在路由发生变化时,只会改变URL中的哈希部分(#后面的路径),且不会向服务器发送新的请求,而是触发 hashchange 事件。
- hash值的改变,都会在浏览器的访问历史中增加一个记录,所以可以通过浏览器的回退、前进按钮控制hash的切换。
- hash路由不会造成404页面的问题,因为所有路由信息都在客户端进行解析和处理,服务端只负责提供应用的初始HTML页面和静态资源,不需要关心路由的匹配问题。
- 通过location.hash 修改hash值,触发更新。
- 通过监听hashchange事件监听浏览器前进或者后退,触发更新
实现
这里有三个文件:一个主页面 hash.html,两个子页面:Home.html 和 About.html
Home.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Home</title>
</head>
<body>Home
</body>
</html>
About.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>About</title>
</head>
<body>About
</body>
</html>
hash.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>哈希路由</title>
</head>
<body><ul><li><a href="Home.html">Home</a></li><li><a href="About.html">About</a></li></ul><div id="content"></div><script>const contentEle = document.querySelector("#content");const ulEle = document.querySelector("ul")const loadHTML = (url, element) => {fetch(url).then(response => {if (response.ok) {return response.text()}throw new Error('Network response was not ok.');}).then(html => {element.innerHTML = html;}).catch(error => {console.error("error => ", error)})}// 页面刷新时,需要重新获取hashwindow.addEventListener('load', () => {// 获取当前的hash值const currentHash = window.location.hash;// 如果存在hash值,执行相应的逻辑if (currentHash) {// 这里可以添加处理hash的逻辑console.log('current hash: ', currentHash.slice(1))loadHTML(currentHash.slice(1), contentEle)}})ulEle.addEventListener('click', (e) => {// 不跳转e.preventDefault()const page = e.target.getAttribute('href')loadHTML(page, contentEle)location.hash = `#${page}`})</script>
</body>
</html>
history 模式
- 在使用history模式时,需要通过服务端支持允许地址可访问,如果没有设置,就很容易导致404的局面;
- 改变url:history 提供了 pushState 和 replaceState 两个方法来记录路由状态,这两个方法只改变URL不会引起页面刷新;
- 监听url变化:通过 popstate 事件监听history 变化,在点击浏览器的前进或者后端功能时触发,在 popstate 事件中根据状态信息加载对应的页面内容
实现
这里有五个文件:主页面一个 history.html; 子页面4个:test1.html、test2.html、test3.html、test4.html
test1.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body>11111
</body>
</html>
test2.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body>2222
</body>
</html>
test3.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body>33333
</body>
</html>
test4.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body>4444
</body>
</html>
history.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body><button id="pushStateBtn">pushState压栈</button><button id="getLenBtn">getLen</button><button id="replaceStateBtn">replaceState替换</button><button id="forward">前进</button><button id="back">后退</button><div>栈:<span id="size"></span></div><hr><div id="content"></div><script>const loadHTML = (url, element) => { fetch(url).then(response => {if (response.ok) {return response.text()}throw new Error('Network response was not ok.');}).then(html => {element.innerHTML = html;}).catch(error => {console.error("error => ", error)})}const pushStateBtn = document.getElementById('pushStateBtn');const getLenBtn = document.getElementById('getLenBtn');const size = document.getElementById('size');const replaceStateBtn = document.getElementById('replaceStateBtn');const forward = document.getElementById('forward');const back = document.getElementById('back');const content = document.getElementById('content');let i = 1;size.textContent = `当前历史记录栈总数:${history.length}`;pushStateBtn.addEventListener('click', () => {const page = `test${i}.html`history.pushState({page: page}, "", page)loadHTML(page, content)i++;}, false)getLenBtn.addEventListener('click', () => {size.textContent = `当前历史记录栈总数:${history.length}`;})forward.addEventListener('click', () => history.forward())back.addEventListener('click', () => history.back())window.addEventListener('popstate', (e) => {console.log("state => ", e.state.page)loadHTML(e.state.page, content)})</script>
</body>
</html>