历史小剧场
唐代实在太高太强了,他们忽忘了民族界限,他们不懂害怕外国人,不懂提防外国人,大量使用外国人当兵作将,结果才弄得不可收拾。于是唐代的府兵一变而成为“藩镇”,军阀割据,胡族临制。----《中国历代政治得失》
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)
})
}
// 页面刷新时,需要重新获取hash
window.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>