前端路由History/Hash原理初窥

14 min read
在传统的多页面时代,网页都是请求一个url,然后后端生成一个完整的html文档,返回给前端。 每次url改变都意味着页面刷新,重新加载新的html文档。这个时候所有路由都是后端控制的。
随着前端技术的发展,前端可以用ajax请求后端数据,便出现了单页面应用。所谓单页面应用,就是只在开始加载一次html文档,之后的操作都是js完成。其中就包括了路由这个功能。
说起来也挺简单,js监听url的改变,js改变dom。
History
<!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>Document</title>
</head>
<body>
<ul>
<li><a href="/">/</a></li>
<li><a href="/page1">page1</a></li>
<li><a href="/page2">page2</a></li>
</ul>
<div class="content-div"></div>
</body>
<script>
class RouterClass {
constructor(path) {
this.routes = {}; // 记录路径标识符对应的cb
history.replaceState({ path }, null, path); // 进入状态
this.routes[path] && this.routes[path]();
window.addEventListener("popstate", e => {// 当用户点击浏览器的前进或者后退触发
console.log(e.state)
const path = e.state && e.state.path;
this.routes[path] && this.routes[path]();
});
}
/* 初始化 */
static init() {
window.Router = new RouterClass(location.pathname);
}
/* 注册路由和回调 */
route(path, cb) {
this.routes[path] = cb || function() {};
}
/* 跳转路由,并触发路由对应回调 */
go(path) {
history.pushState({ path }, null, path);
console.log(history);
this.routes[path] && this.routes[path]();
}
}
RouterClass.init();
const ul = document.querySelector("ul");
const ContentDom = document.querySelector(".content-div");
const changeContent = content => (ContentDom.innerHTML = content);
Router.route("/", () => changeContent("默认页面"));
Router.route("/page1", () => changeContent("page1页面"));
Router.route("/page2", () => changeContent("page2页面"));
ul.addEventListener("click", e => {
console.log(e.target.tagName);
if (e.target.tagName === "A") {
e.preventDefault();
Router.go(e.target.getAttribute("href"));
}
});
</script>
</html>
Hash
URL 片段是哈希标记 #
前面的名称,哈希标记指定当前文档中的内部目标位置(HTML 元素的 ID)。
#
最开始学习html时候,称之为锚点定位,也有叫他页面内部的超链接的。#
后面跟的是元素 id。
如果<a href="#demo"/>
点击页面就会定位 demo 这个元素所在的地方。
另外值得一提的是,#
或者top
会跳到页面最顶部。
hash 路由就是借用这个 #
号来实现的。因为每次 #
后面的内容改变,都有一个 hashchang
事件。大概原理如下:
function Router(){
this.routes = [];
this.currentUrl = '';
}
Router.prototype.add = function(path,callback){
this.routes[path] = callback || function(){};
}
Router.prototype.refresh = function(){
this.currentUrl = location.hash.replace(/^#*/,'');
this.routes[this.currentUrl]();
}
Router.prototype.load = function(){
window.addEventListener('load',this.refresh.bind(this),false);
window.addEventListener('hashchange',this.refresh.bind(this),false);
}
Router.prototype.nevigate = function(path){
path = path ? path : "";
location.href = location.href.replace(/#(.*)$/,'')+'#'+path;
}
window.Router = new Router();
window.Router.load();
//使用
Router.add('/',function(){
document.getElementById('demo').innerHTML="Router home!!!";
});
Router.add('/blue',function(){
document.getElementById('demo').innerHTML="Router blue!!!";
});
当然实际使用需要处理更多情况。