路由的基本功能是为了保证视图和URL的同步,而视图可以看成是资源的一种表现。
目前,前端的主流Vue、React、Angular这些,他们都提供了有关路由的插件,一般来说,这些路由插件总是提供两种不同方式的路由方式: Hash 和 History。具体内容将会在下文中提到,下面就让我们围绕这两种方式来简单手写一个路由:
1、Hash模式
Hash的基本概念:Hash 方法是在路由中带有一个 #,主要原理是通过监听 # 后的 URL 路径标识符的更改而触发的浏览器 hashchange 事件,然后通过获取 location.hash 得到当前的路径标识符,再进行一些路由跳转的操作,参见 MDN
首先我们先建立一个index.html文件,在body标签中开始hash的编写:
<button type="button" onclick="history.go(-1)">返回</button>
<h2>push模式</h2>
<ul>
<li onclick="Router.push('/')">首页</li>
<li onclick="Router.push('/news')">新闻</li>
<li onclick="Router.push('/product')">产品</li>
</ul>
<div id="app"></div>
<script>
var app = document.getElementById("app");
function RouterClass() {
this.routes={};
this.curUrl=""; //初始url
this.eventHashRouter();
}
RouterClass.prototype.route = function(path, callback) {
this.routes[path] = callback || function(){}
}
// 监听hash模式路由
RouterClass.prototype.eventHashRouter = function() {
window.addEventListener("hashchange", this.hashRouter.bind(this))
}
RouterClass.prototype.hashRouter = function() {
this.curUrl = window.location.hash.slice(1) || '/';
// console.log(this.curUrl);
this.routes[this.curUrl]();
}
// push模式页面跳转
RouterClass.prototype.push = function(url) {
console.log(url);
url = "#" +url;
window.location.href = url;
}
var Router = new RouterClass() //初始化 使用
// 构造一个函数,根据url 改变 #app 中的内容,对页面进行渲染
Router.route('/', function() {
app.innerHTML="首页"
})
Router.route('/news', function() {
app.innerHTML="新闻页面"
})
Router.route('/product', function() {
app.innerHTML="产品页面"
})
</script>
这样,hash模式的路由就基本实现啦,正常的话,点击首页、新闻、产品应该可以在 #app 中显示相关信息了,但是这里有小个bug, 就是一但刷新页面,数据就会丢失。另外push本身会记录你的点击记录,当你想要通过返回按钮返回时,它会根据你的点击记录来返回,这样就让你无法实现像 在点击许多页面后,想通过返回按钮直接返回首页的功能,这里就要用到replace来解决了。
接下来就让我们来解决上面的两个问题,请看下面的代码:
<h2>replace模式</h2>
<ul>
<!-- hash模式 就是带 # 号的 -->
<li onclick="Router.replace('/')">首页</li>
<li onclick="Router.replace('/news')">新闻</li>
<li onclick="Router.replace('/product')">产品</li>
</ul>
// 监听hash模式路由
RouterClass.prototype.eventHashRouter = function() {
// 监听load事件,防止刷新页面数据丢失
window.addEventListener("load", this.hashRouter.bind(this));
window.addEventListener("hashchange", this.hashRouter.bind(this))
}
//replace模式页面跳转
RouterClass.prototype.replace = function(url) {
url = "#" +url;
window.location.replace(url);
}
恭喜恭喜,问题解决啦,至此,路由的hash模式就圆满结束喽。
2、history模式
接下来就让我们来看看路由的另外一个 history 模式吧
我们来修改下我们现在的代码:
<script>
// baseUrl 是根路径
var app = document.getElementById("app"), baseUrl = "/router/";
function RouterClass(opts) {
this.routes={};
this.curUrl="";
this.mode="";
if(opts){
this.mode=opts.mode;
if(this.mode==='history'){
this.eventHistoryRouter();
}else{
this.eventHashRouter();
}
}else {
this.eventHashRouter();
}
}
RouterClass.prototype.route = function(path, callback) {
this.routes[path] = callback || function(){}
}
// 监听hash模式路由
RouterClass.prototype.eventHashRouter = function() {
// 监听load事件,防止刷新页面数据丢失
window.addEventListener("load", this.hashRouter.bind(this));
window.addEventListener("hashchange", this.hashRouter.bind(this))
}
//hash模式
RouterClass.prototype.hashRouter = function() {
this.curUrl = window.location.hash.slice(1) || '/';
// console.log(this.curUrl);
this.routes[this.curUrl]();
}
// history模式
RouterClass.prototype.historyRouter = function() {
this.curUrl = window.location.pathname;
this.routes[this.curUrl]();
}
// 监听history模式
RouterClass.prototype.eventHistoryRouter = function() {
window.addEventListener("load", this.historyRouter.bind(this));
// 监听回退事件 打个比方:就是你点浏览器那个返回的箭头按钮时触发的事件
window.addEventListener("popstate", this.historyRouter.bind(this));
}
// push模式页面跳转
RouterClass.prototype.push = function(url) {
if(this.mode === 'history') {
window.history.pushState({},null,url);
this.routes[url]();
}else{
url = "#" +url;
window.location.href = url;
}
}
//replace模式页面跳转
RouterClass.prototype.replace = function(url) {
if(this.mode==='history'){
window.history.replaceState({},null,url);
this.routes[url]();
}else {
url = "#" + url;
window.location.replace(url);
}
}
var Router = new RouterClass({
mode:"history" //hash:带#号,history:不带#号
});
// 构造一个函数,根据url 改变 #app 中的内容,对页面进行渲染
Router.route(baseUrl,function(){
app.innerHTML="首页"
})
Router.route(baseUrl+'news',function(){
app.innerHTML="新闻页面"
})
Router.route(baseUrl+'product',function(){
app.innerHTML="产品页面"
})
</script>
我再来补充解释一下history模式下页面跳转用到的 history.pushState() 和 history.replaceState() 方法,这两个是HTML5新引入的方法,它们分别可以添加和修改历史记录条目,更详细的可以参考一下MDN上的相关解释:MDN。
另外,再提示一下,如果你直接文件夹下双击打开 html 就会报一个这样的错:
这需要用服务器来运行,把html文件丢到服务器上就没事了,我丢到了Apache上再用Apache运行,错误就解决了。你以为到这就结束了吗,其实还有bug,你如果F5刷新的话,它就会给你报一个经典的 404 Not Found。
这个问题又该如何解决呢,Apache 的话可以通过配置.htaccess解决:
<IfModule mod_rewrite.c>
Options Indexes FollowSymLinks ExecCGI
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /router/index.html [L]
#ErrorDocument 404 /router/index.html
</IfModule>
你要改的就是RewriteRule这里,也就是url重写,把这个保存为.htaccess文件后跟你的html一起放在Apache文件夹里面,如:
我在router文件夹中放入了这两项。终于,我们完成了使用原生js实现路由,恭喜恭喜。
最后来把完整的代码发一下吧:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>手写router</title>
</head>
<body>
<button type="button" onclick="history.go(-1)">返回</button>
<h2>push模式</h2>
<ul>
<li onclick="Router.push(baseUrl)">首页</li>
<li onclick="Router.push(baseUrl+'news')">新闻</li>
<li onclick="Router.push(baseUrl+'product')">产品</li>
</ul>
<h2>replace模式</h2>
<ul>
<li onclick="Router.replace(baseUrl)">首页</li>
<li onclick="Router.replace(baseUrl+'news')">新闻</li>
<li onclick="Router.replace(baseUrl+'product')">产品</li>
</ul>
<div id="app"></div>
<script>
// baseUrl 是根路径
var app = document.getElementById("app"), baseUrl = "/router/";
function RouterClass(opts) {
this.routes={};
this.curUrl="";
this.mode="";
if(opts){
this.mode=opts.mode;
if(this.mode==='history'){
this.eventHistoryRouter();
}else{
this.eventHashRouter();
}
}else {
this.eventHashRouter();
}
}
RouterClass.prototype.route = function(path, callback) {
this.routes[path] = callback || function(){}
}
// 监听hash模式路由
RouterClass.prototype.eventHashRouter = function() {
// 监听load事件,防止刷新页面数据丢失
window.addEventListener("load", this.hashRouter.bind(this));
window.addEventListener("hashchange", this.hashRouter.bind(this))
}
//hash模式
RouterClass.prototype.hashRouter = function() {
this.curUrl = window.location.hash.slice(1) || '/';
// console.log(this.curUrl);
this.routes[this.curUrl]();
}
// history模式
RouterClass.prototype.historyRouter = function() {
this.curUrl = window.location.pathname;
this.routes[this.curUrl]();
}
// 监听history模式
RouterClass.prototype.eventHistoryRouter = function() {
window.addEventListener("load", this.historyRouter.bind(this));
// 监听回退事件 打个比方:就是你点浏览器那个返回的箭头按钮时触发的事件
window.addEventListener("popstate", this.historyRouter.bind(this));
}
// push模式页面跳转
RouterClass.prototype.push = function(url) {
if(this.mode === 'history') {
window.history.pushState({},null,url);
this.routes[url]();
}else{
url = "#" +url;
window.location.href = url;
}
}
//replace模式页面跳转
RouterClass.prototype.replace = function(url) {
if(this.mode==='history'){
window.history.replaceState({},null,url);
this.routes[url]();
}else {
url = "#" + url;
window.location.replace(url);
}
}
//初始化 使用
var Router = new RouterClass({
mode:"history" //hash:带#号,history:不带#号
});
// 构造一个函数,根据url 改变 #app 中的内容,对页面进行渲染
Router.route(baseUrl,function(){
app.innerHTML="首页"
})
Router.route(baseUrl+'news',function(){
app.innerHTML="新闻页面"
})
Router.route(baseUrl+'product',function(){
app.innerHTML="产品页面"
})
</script>
</body>
</html>
到这里就结束啦,希望你能通过本文有所收获,我的文章都是学习过程中的总结,如有错误,欢迎指出,感谢阅读。
本网站是一个以CSS、JavaScript、Vue、HTML为核心的前端开发技术网站。我们致力于为广大前端开发者提供专业、全面、实用的前端开发知识和技术支持。 在本网站中,您可以学习到最新的前端开发技术,了解前端开发的最新趋势和最佳实践。我们提供丰富的教程和案例,让您可以快速掌握前端开发的核心技术和流程。 本网站还提供一系列实用的工具和插件,帮助您更加高效地进行前端开发工作。我们提供的工具和插件都经过精心设计和优化,可以帮助您节省时间和精力,提升开发效率。 除此之外,本网站还拥有一个活跃的社区,您可以在社区中与其他前端开发者交流技术、分享经验、解决问题。我们相信,社区的力量可以帮助您更好地成长和进步。 在本网站中,您可以找到您需要的一切前端开发资源,让您成为一名更加优秀的前端开发者。欢迎您加入我们的大家庭,一起探索前端开发的无限可能!参考:
history | MDN
hashchange | MDN
Manipulating the browser history | MDN
History 对象 -- JavaScript 标准参考教程
代办报建
本公司承接江浙沪报建代办施工许可证。
联系人:张经理,18321657689(微信同号)。
19条评论
论坛的人气越来越旺了!http://wtcs.cqyiyou.net/test/054409284.html
帖子好乱!http://bbs.kdhlpt.com/article/5343578.html
怎么我回帖都没人理我呢?http://gft0a6.toroferrer.com
支持楼上的!http://xlg.gdmhvpe.cn
管它三七二十一!http://b7dr00.kunzhipeng.cn
我只是来赚积分的!http://www.hardknox.cn
顶一个!http://xcz.lyjmshop.com
楼主的头像能辟邪啊!http://26s.minidv100.com
赞一个!http://sb4lvf.ah-olymmail.com
坚持回帖!http://5vdkbs.ybndd.com
缺乏激情了!http://rjjl.glrainbow.com
在这个版块混了这么久了,第一次看见这么给你的帖子!http://6psj.luckinggo.com
楼上的别说的那么悲观好吧!http://ap0.mrzrw.cn
楼主是好人!http://6rl1m.youjiaoba.com
支持一个http://a9qz.uniqtest.net
终于看完了,很不错!http://9y2.youjiaoba.com
最近压力山大啊!http://www.guangcexing.net/voddetail/pGwuMTqZg.html
楼主就是我的榜样哦http://www.guangcexing.net/voddetail/TmrJXwMukX.html
我只看看不说话。。。http://www.guangcexing.net/voddetail/aCsJpAXsEF.html
发表评论