#yyds干货盘点#History 与 hash
精选
原创
©著作权归作者所有:来自51CTO博客作者尼羲的原创作品,请联系作者获取转载授权,否则将追究法律责任
url 组成
//http://127.0.0.1:8001/01-hash.html?a=100&b=20#/aaa/bbb location.protocal //
'http:' 协议 localtion.hostname // '127.0.0.1' 主机名 location.host //
'127.0.0.1:8001' 主机 location.port //8001 端口号 location.pathname
//'01-hash.html' 访问页面 location.serach // '?a=100&b=20' 搜索内容
location.hash // '#/aaa/bbb' 哈希值
hash 特点
概述
- hash 本身用于页面定位
- hash 变化会触发网页跳转,即浏览器的前进和后退。
hash
可以改变 url
,但是不会触发页面重新加载(hash 的改变是记录在 window.history
中),即不会刷新页面。也就是说,所有页面的跳转都是在客户端进行操作。因此,这并不算是一次 http
请求,所以这种模式不利于 SEO
优化。hash
只能修改 #
后面的部分,所以只能跳转到与当前 url
同文档的 url
。hash
通过 window.onhashchange
的方式,来监听 hash
的改变,借此实现无刷新跳转的功能。hash
永远不会提交到 server
端(可以理解为只在前端自生自灭)。
实现前端路由
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Hash 模式</title>
</head>
<body>
<div>
<ul>
<li><a href="#/page1">page1</a></li>
<li><a href="#/page2">page2</a></li>
</ul>
<!--渲染对应组件的地方-->
<div id="route-view"></div>
</div>
<script type="text/javascript">
// 第一次加载的时候,不会执行 hashchange 监听事件,默认执行一次
// DOMContentLoaded 为浏览器 DOM 加载完成时触发
window.addEventListener("DOMContentLoaded", Load);
window.addEventListener("hashchange", HashChange);
// 展示页面组件的节点
var routeView = null;
function Load() {
routeView = document.getElementById("route-view");
HashChange();
}
function HashChange() {
// 每次触发 hashchange 事件,通过 location.hash 拿到当前浏览器地址的 hash 值
// 根据不同的路径展示不同的内容
switch (location.hash) {
case "#/page1":
routeView.innerHTML = "page1";
return;
case "#/page2":
routeView.innerHTML = "page2";
return;
default:
routeView.innerHTML = "page1";
return;
}
}
</script>
</body>
</html>
封装
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Hash 模式</title>
</head>
<body>
<div>
<!-- html:菜单中href设置为hash形式,id为app中放置页面内容 -->
<ul id="menu">
<li><a href="#index">首页</a></li>
<li><a href="#news">资讯</a></li>
<li><a href="#user">个人中心</a></li>
</ul>
<div id="app"></div>
</div>
<script type="text/javascript">
class Router {
constructor() {
this.routers = []; //存放我们的路由配置
}
add(route, callback) {
this.routers.push({
path: route,
render: callback,
});
}
listen(callback) {
window.onhashchange = this.hashChange(callback);
this.hashChange(callback)(); //首次进入页面的时候没有触发hashchange,必须要就单独调用一下
}
hashChange(callback) {
let self = this;
return function () {
let hash = location.hash;
console.log(hash);
for (let i = 0; i < self.routers.length; i++) {
let route = self.routers[i];
if (hash === route.path) {
callback(route.render());
return;
}
}
};
}
}
let router = new Router();
router.add("#index", () => {
return "<h1>这是首页内容</h1>";
});
router.add("#news", () => {
return "<h1>这是新闻内容</h1>";
});
router.add("#user", () => {
return "<h1>这是个人中心内容</h1>";
});
console.log(router);
router.listen((renderHtml) => {
let app = document.getElementById("app");
app.innerHTML = renderHtml;
});
</script>
</body>
</html>
histroy
概述
History.length
:当前窗口访问过的网址数量(包括当前网页)History.state
:History 堆栈最上层的状态值History.back()
:移动到上一个网址,等同于点击浏览器的后退键。对于第一个访问的网址,该方法无效果。History.forward()
:移动到下一个网址,等同于点击浏览器的前进键。对于最后一个访问的网址,该方法无效果。History.go()
:接受一个整数作为参数,以当前网址为基准,移动到参数指定的网址,比如 go(1)
相当于 forward()
,go(-1)
相当于 back()
。如果参数超过实际存在的网址范围,该方法无效果;如果不指定参数,默认参数为 0
,相当于刷新当前页面。History.pushState()
方法用于在历史中添加一条记录。该方法接受三个参数,依次为:
state
:一个与添加的记录相关联的状态对象,主要用于 popstate
事件。该事件触发时,该对象会传入回调函数。也就是说,浏览器会将这个对象序列化以后保留在本地,重新载入这个页面的时候,可以拿到这个对象。如果不需要这个对象,此处可以填 null
。title
:新页面的标题。但是,现在所有浏览器都忽视这个参数,所以这里可以填空字符串。url
:新的网址,必须与当前页面处在同一个域。浏览器的地址栏将显示这个网址。pushState()
方法不会触发页面刷新,只是导致 History 对象发生变化,地址栏会有反应。- 如果
pushState
的 URL 参数设置了一个新的锚点值(即 hash
),并不会触发 hashchange
事件。反过来,如果 URL 的锚点值变了,则会在 History 对象创建一条浏览记录。 - 如果
pushState()
方法设置了一个跨域网址,则会报错。
History.replaceState()
方法用来修改 History 对象的当前记录,其他都与 pushState()
方法一模一样popstate
事件,每当同一个文档的浏览历史(即 history
对象)出现变化时,就会触发 popstate
事件。
- 注意,仅仅调用
pushState()
方法或 replaceState()
方法 ,并不会触发该事件,只有用户点击浏览器倒退按钮和前进按钮,或者使用 JavaScript 调用 History.back()
、History.forward()
、History.go()
方法时才会触发。另外,该事件只针对同一个文档,如果浏览历史的切换,导致加载不同的文档,该事件也不会触发。
window.onpopstate = function (event) {
console.log("location: " + document.location);
console.log("state: " + JSON.stringify(event.state));
};
// 或者
window.addEventListener("popstate", function (event) {
console.log("location: " + document.location);
console.log("state: " + JSON.stringify(event.state));
});
实现前端路由(需要项目启动)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>History 模式</title>
</head>
<body>
<div>
<ul>
<li><a href="/page1">page1</a></li>
<li><a href="/page2">page2</a></li>
</ul>
<div id="route-view"></div>
</div>
<script type="text/javascript">
window.addEventListener("DOMContentLoaded", Load);
window.addEventListener("popstate", PopChange);
var routeView = null;
function Load() {
routeView = document.getElementById("route-view");
// 默认执行一次 popstate 的回调函数,匹配一次页面组件
PopChange();
// 获取所有带 href 属性的 a 标签节点
var aList = document.querySelectorAll("a[href]");
// 遍历 a 标签节点数组,阻止默认事件,添加点击事件回调函数
aList.forEach((aNode) =>
aNode.addEventListener("click", function (e) {
e.preventDefault(); //阻止a标签的默认事件
var href = aNode.getAttribute("href");
// 手动修改浏览器的地址栏
history.pushState(null, "", href);
// 通过 history.pushState 手动修改地址栏,
// popstate 是监听不到地址栏的变化,所以此处需要手动执行回调函数 PopChange
PopChange();
})
);
}
function PopChange() {
console.log("location", location);
switch (location.pathname) {
case "/page1":
routeView.innerHTML = "page1";
return;
case "/page2":
routeView.innerHTML = "page2";
return;
default:
routeView.innerHTML = "page1";
return;
}
}
</script>
</body>
</html>
封装
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>history 模式</title>
</head>
<body>
<div>
<!-- html:菜单中href设置为hash形式,id为app中放置页面内容 -->
<ul id="menu">
<li><a href="/index">首页</a></li>
<li><a href="/news">资讯</a></li>
<li><a href="/user">个人中心</a></li>
</ul>
<div id="app"></div>
</div>
<script type="text/javascript">
class Router {
constructor() {
this.routers = [];
this.renderCallback = null;
}
add(route, callback) {
this.routers.push({
path: route,
render: callback,
});
}
pushState(path, data = {}) {
window.history.pushState(data, "", path);
this.renderHtml(path);
}
listen(callback) {
this.renderCallback = callback;
this.changeA();
window.onpopstate = () => this.renderHtml(this.getCurrentPath());
this.renderHtml(this.getCurrentPath());
}
changeA() {
document.addEventListener("click", (e) => {
if (e.target.nodeName === "A") {
e.preventDefault();
let path = e.target.getAttribute("href");
this.pushState(path);
}
});
}
getCurrentPath() {
return location.pathname;
}
renderHtml(path) {
for (let i = 0; i < this.routers.length; i++) {
let route = this.routers[i];
if (path === route.path) {
this.renderCallback(route.render());
return;
}
}
}
}
let router = new Router();
router.add("/index", () => {
return "<h1>这是首页内容</h1>";
});
router.add("/news", () => {
return "<h1>这是新闻内容</h1>";
});
router.add("/user", () => {
return "<h1>这是个人中心内容</h1>";
});
router.listen((renderHtml) => {
let app = document.getElementById("app");
app.innerHTML = renderHtml;
});
</script>
</body>
## 参考
https://juejin.cn/post/6993840419041706014
https://cloud.tencent.com/developer/article/1904982
https://wangdoc.com/javascript/bom/history