前端路由实现方式,主要有两种,分别是history和hash模式。

hash模式

不同路由对应的hash是不一样的,如何能够监听到URL中关于hash部分发生的变化?浏览器已经暴露给我们一个现成的方法hashchange,在hash改变的时候,触发该事件。

实现示例代码:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title></title>
</head>
<style>
* {
margin: 0;
padding: 0;
box-sizing:border-box;
}
html,body {
height: 100%;
}
#content {
height: calc(100% - 50px);
display: flex;
align-items: center;
justify-content: center;
font-size: 30px;
}
#nav {
width: 100%;
height: 50px;
position: fixed;
bottom: 0;
left: 0;
display: flex;
}
#nav a {
width: 25%;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid black;
}
#nav a:not(:last-of-type) {
border-right: none;
}
</style>
<body>
<div id="content">内容区域</div>
<div id="nav">
<a href="#/">首页</a>
<a href="#/new-product">新品</a>
<a href="#/shopping-cart">购物车</a>
<a href="#/my">我的</a>
</div>
<script type="text/javascript">
class VueRouter {
constructor(routes = []) {
this.routes = routes;
this.currentHash = ''; // 当前hash值
window.addEventListener('load',this.refresh.bind(this),false);
window.addEventListener('hashchange',this.refresh.bind(this),false);
} // 获取hash
getHash(url) {
return url.indexOf('#') !== -1 ? url.slice(url.indexOf('#') + 1) : '/'
} refresh(event) {
let newHash = '';
if(event.newURL) {
newHash = this.getHash(event.newURL || '');
} else {
newHash = this.getHash(window.location.href);
}
this.currentHash = newHash;
this.matchComponentCon();
} // 匹配hash组件内容
matchComponentCon() {
let curRoute = this.routes.find(route => route.path === this.currentHash);
if(!curRoute) {
curRoute = this.routes.find(route => route.path === '/');
}
const { component } = curRoute;
document.querySelector('#content').innerHTML = component;
}
} const router = new VueRouter([
{path: '/',name: 'home',component: '<div>首页内容</div>'},
{path: '/new-product',name: 'new-product',component: '<div>新品内容</div>'},
{path: '/shopping-cart',name: 'shopping-cart',component: '<div>购物车内容</div>'},
{path: '/my',name: 'my',component: '<div>我的内容</div>'}
])
</script>
</body>
</html>

效果如图所示:

当点击底部不同的菜单,中间区域显示对应菜单的内容。

history模式

HTML5提供的一个history全局对象,这个对象包含了关于我们访问网页(历史会话)的一些信息,history路由的实现要归功于它。

history它还暴露了一些方法,如:

  • window.history.go: 可以跳转到浏览器会话历史中的指定的某一个记录页
  • window.history.forward: 指向浏览器会话历史中的下一页,跟浏览器的前进按钮相同
  • window.history.back: 返回浏览器会话历史中的上一页,跟浏览器的回退按钮功能相同
  • window.history.pushState: 可以将给定的数据压入到浏览器会话历史栈中
  • window.history.replaceState: 将当前的会话页面的url替换成指定的数据

而history路由的实现,主要就是依靠于pushState与replaceState实现的。这两个方法的特点:

  • 改变当前页面的URL,不会刷新页面;
  • 调用pushState方法会把当前的URL压入到浏览器的会话历史栈中,会让history.length加1,而replaceState是替换当前的这条会话历史,不会增加history.length;

我们是否可以通过pushStatereplaceState能够监听URL变化这个动作,就可以实现不同路由页面的渲染处理,我们需要了解下事件处理程序popState,官网参考地址:https://developer.mozilla.org/zh-CN/docs/Web/API/Window/popstate_event

每当激活同一文档中不同的历史记录条目时,popstate 事件就会在对应的window对象上触发。如果当前处于激活状态的历史记录条目是由history.pushState()方法创建的或者是由 history.replaceState() 方法修改的,则 popstate 事件的 state 属性包含了这个历史记录条目的 state 对象的一个拷贝。

备注: 调用 history.pushState() 或者 history.replaceState() 不会触发 popstate 事件。popstate 事件只会在浏览器某些行为下触发,比如点击后退按钮(或者在 JavaScript 中调用 history.back() 方法)。即,在同一文档的两个历史记录条目之间导航会触发该事件。

从官网描述看,总结如下:

  • history.pushStatehistory.replaceState方法不会触发popstate事件
  • 浏览器的某些行为会导致popstate事件,比如go、back、forward
  • popstate事件对象中的state属性,可以理解是我们在通过history.pushStatehistory.replaceState方法时,传入的指定的数据

结论就是popstate无法监听history.pushStatehistory.replaceState方法,既然不支持,那么我们重新写下这个history.pushStatehistory.replaceState方法。在这个方法中,也能够暴露出自定义的全局事件,然后再监听自定义的事件

就行了。

let _wr = function(type) {
let orig = history[type];
return function() {
orig.apply(this,arguments);
let e = new Event(type);
e.arguments = arguments;
window.dispatchEvent(e);
}
} history.pushState = _wr('pushState');
history.replaceState = _wr('replaceState');

执行上面的方法,相当于自定义了同名的pushStatereplaceState自定义事件的触发绑定到了window上面,即是可以通过pushSate或者replaceState来执行自定义的事件,同时还执行了浏览器的history.pushState或者history.replaceState方法。

简单实现:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title></title>
</head>
<style>
* {
margin: 0;
padding: 0;
box-sizing:border-box;
}
html,body {
height: 100%;
}
#content {
height: calc(100% - 50px);
display: flex;
align-items: center;
justify-content: center;
font-size: 30px;
}
</style>
<body>
<div id="content">内容区域</div>
<button id="button1">首页</button>
<button id="button2">新品</button>
<button id="button3">购物车</button>
<button id="button4">我的</button> <script type="text/javascript">
const button1 = document.querySelector('#button1');
const button2 = document.querySelector('#button2');
const button3 = document.querySelector('#button3');
const button4 = document.querySelector('#button4'); let _wr = function(type) {
let orig = history[type];
return function() {
orig.apply(this,arguments);
let e = new Event(type);
e.arguments = arguments;
window.dispatchEvent(e);
}
} history.pushState = _wr('pushState');
history.replaceState = _wr('replaceState'); button1.addEventListener('click',() => {
const path = './home';
const title = '首页';
history.pushState({ path,title },title, path);
})
button2.addEventListener('click',() => {
const path = './new-product';
const title = '新品';
history.pushState({ path,title },title, path);
})
button3.addEventListener('click',() => {
const path = './shopping-cart';
const title = '购物车';
history.pushState({ path,title },title, path);
})
button4.addEventListener('click',() => {
const path = './my';
const title = '我的';
history.pushState({ path,title },title, path);
}) class VueRouter {
constructor(routes = []) {
this.routes = routes;
this.currentUrl = '';
this.matchComponentCon();
window.addEventListener('pushState', e => {
console.log(e)
this.currentUrl = e.arguments[2] && e.arguments[2].slice(e.arguments[2].indexOf('.') + 1);
window.document.title = e.arguments[1];
this.matchComponentCon();
},false) // 监听浏览器的back、forward、go事件
window.addEventListener('popstate', e => {
const { path = '/', title } = e.state || {};
console.log(e)
this.currentUrl = path.slice(path.indexOf('.') + 1);
window.document.title = title;
this.matchComponentCon();
},false)
} // 匹配路由页面内容
matchComponentCon() {
let curRoute = this.routes.find(route => route.path === this.currentUrl);
if(!curRoute) {
curRoute = this.routes.find(route => route.path === '/');
}
const { component } = curRoute;
document.querySelector('#content').innerHTML = component;
}
} const router = new VueRouter([
{path: '/',name: 'home',component: '<div>首页内容</div>'},
{path: '/new-product',name: 'new-product',component: '<div>新品内容</div>'},
{path: '/shopping-cart',name: 'shopping-cart',component: '<div>购物车内容</div>'},
{path: '/my',name: 'my',component: '<div>我的内容</div>'}
]) </script>
</body>
</html>

history模式需要后端配合解决刷新页面404的问题。nginx配置如下:

location / {
try_files $uri $uri/ /index.html;
}

小tips:怎样实现简单的前端hash与history路由方式?的更多相关文章

  1. hash和history路由的区别

    在了解路由模式前,我们先看下 什么是单页面应用,vue-router  的实现原理是怎样的,这样更容易理解路由. SPA与前端路由 SPA(单页面应用,全程为:Single-page Web appl ...

  2. 简单的基于hash和hashchange的前端路由

    hash定义 hash这个玩意是地址栏上#及后面部分,代表网页中的一个位置,#后面部分为位置标识符.页面打开后,会自动滚动到指定位置处. 位置标识符 ,一是使用锚点,比如<a name=&quo ...

  3. 前端路由hash、history原理及简单的实践下

    阅读目录 一:什么是路由?前端有哪些路由?他们有哪些特性? 二:如何实现简单的hash路由? 三:如何实现简单的history路由? 四:hash和history路由一起实现 回到顶部 一:什么是路由 ...

  4. 前端必备,5大mock省时提效小tips,用了提前下班一小时

    ​ 一.一些为难前端的业务场景 在我的工作经历里,需要等待后端童鞋配合我的情形大概有以下几种: a.我们跟外部有项目合作,需要调用到第三方接口. 一般这种情况下,商务那边谈合同,走流程,等第三方审核, ...

  5. 前端防错以及好用小tips指南总结

    @前端防錯以及好用小tips指南總結 1.一般情況下我們接收到的都是對象格式,某些情況下,需要接到後端傳過來的奇怪的字符串格式的JSON,需要解析成對象,但是有時候他們傳過來的格式有問題,會報錯 解決 ...

  6. Windows7驱动调试小Tips

    v:* { } o:* { } w:* { } .shape { }p.MsoNormal,li.MsoNormal,div.MsoNormal { margin: 0cm; margin-botto ...

  7. 你不知道的JavaScript--Item17 循环与prototype最后的几点小tips

    1.优先使用数组而不是Object类型来表示有顺序的集合 ECMAScript标准并没有规定对JavaScript的Object类型中的属性的存储顺序. 但是在使用for..in循环对Object中的 ...

  8. 微信小程序web-view的简单思考和实践

    微信小程序的组件web-view推出有一段时间了,这个组件的推出可以说是微信小程序开发的一个重要事件,让微信小程序不会只束缚在微信圈子里了,打开了一个口子,这个口子或许还比较小,但未来有无限可能. 简 ...

  9. 整理一些《纸书科学计算器》的小Tips

    本文最开始是在2016年的文章 Win10应用<纸书科学计算器>更新啦! 发表之后撰写的,当时那篇文章收到了不少人点赞,应用在国内市场的日下载量也突然上涨,让我感到受宠若惊,这里要感谢Wp ...

  10. 小tips: zoom和transform:scale的区别

    小tips: zoom和transform:scale的区别 转自 张鑫旭 前端大神 by zhangxinxu from http://www.zhangxinxu.com本文地址:http://w ...

随机推荐

  1. IDEA之NexChatGPT插件【工欲善其事必先利其器】

    国内有热心的程序员开发了一款NexChatGPT插件,安装后开箱即用十分方便,打字机展示的效果也很流畅,另外插件内还外链了国内能直接访问的ChatGPT,非常推荐试一下,IDEA插件NexChatGP ...

  2. 面试官:Dubbo一次RPC请求经历哪些环节?

    大家好,我是三友~~ 今天继续探秘系列,扒一扒一次RPC请求在Dubbo中经历的核心流程. 本文是基于Dubbo3.x版本进行讲解 一个简单的Demo 这里还是老样子,为了保证文章的完整性和连贯性,方 ...

  3. git 怎么将某个开发分支最近几次的提交合并成一次提交

    1. 切换到开发分支: git checkout dev 2. 运行交互式 rebase 命令,并指定要合并的提交数量(在这个例子中是最近的3次提交): git rebase -i HEAD~3 3. ...

  4. 解码 xsync 的 map 实现

    解码 xsync 的 map 实现 最近在寻找 Go 的并发 map 库的时候,翻到一个 github 宝藏库,xsync (https://github.com/puzpuzpuz/xsync) . ...

  5. oeasy教您玩转vim - 8 - # 追加文本

    追加文本 回忆上节课内容 我们这次深入了 i 命令 i 在当前的光标之前插入 I 在本行文本的最前面插入 还有一些常用的编辑命令 . 重做 u 撤销 ctrl+r 还原 关于插入,还有什么讲究吗? 类 ...

  6. Python 函数中箭头 (->)的用处

    Python 3 -> 是函数注释的一部分,表示函数返回值的类型. def useful_function(x) -> int: # Useful code, using x, here ...

  7. layui下拉框的数据如何直接从数据库提取(动态赋值)

    代码说明部分 第一步:先把layui官方给的模板粘到自己的前端注:下面的代码是我直接从layui官网粘过来的 <div class="layui-form-item"> ...

  8. 一键导入抓包数据生成HTTP请求

    Jmeter一键导入抓包数据生成HTTP请求.路径:工具->Import from cURL 在弹框里粘贴cURL,点击"Create Test Plan"会自动生成HTTP ...

  9. .NET 8 通用权限框架 前后端分离,开箱即用

    前言​ 推荐一个基于.NET 8 实现的通用权限开发框架Admin.NET,前端使用Vue3/Element-plus开发. 基于.NET 8(Furion)/SqlSugar实现的通用管理平台.整合 ...

  10. 【微信小程序】 使用NPM包与VantWeapp

    小程序对npm的支持与限制 目前,小程序中已经支持使用npm安装第三方包,从而来提高小程序的开发效率. 但是,在小程序中使用npm包有如下3个限制: ① 不支持依赖于Node.js内置库的包② 不支持 ...