文章目录
通过这篇文章,你可以了解到
- 为什么需要前端路由?解决了什么问题?
- 前端路由的基本原理是什么?
- hash路由的hash值会发送到服务端吗?
- history路由为什么需要服务端支持?
- hash和history两种模式怎么选?
如果这些你都了解,可以看我另一篇博客:前端路由:实现篇_Palate的博客-CSDN博客
一、为什么需要前端路由
1. 什么是前端路由
- 前端路由,就是一个前端不同页面的状态管理器,可以不向后台发送请求而直接通过前端技术实现多个页面的效果。
2. 单页面应用的问题
- 最开始的网页是多页面的,后来出现了
Ajax
之后,才慢慢有了SPA
,SPA
应用指的是应用只有一个主页面,通过动态替换DOM内容并同步修改url地址,来模拟多页应用的效果,切换页面的功能直接由前台脚本来完成,而不是由后端渲染完毕后前端只负责显示。 AJAX
局部刷新,导致浏览器的URL
不会发生任何变化而完成了请求,从而破坏了用户浏览体验。同时本次浏览的页面内容在用户下次使用URL
访问时将无法重新呈现,使用路由可以很好地解决这个问题。
3. 前端路由的作用
前端路由,解决SPA
的两个弊端:
- 用户在使用的过程中,url 不会发生任何改变。当用户操作了几步之后,一不小心刷新了页面,又会回到最开始的状态。
- 由于缺乏 url,不方便搜索引擎进行收录。
3. 两种实现方式
-
在单页面web网页中, 有两种实现方式
hash模式
:修改URL
中hash
值,触发hashchange
事件去渲染不同的内容,history模式
:利用history API
实现``URL地址改变,触发popstate
事件渲染不同的内容
-
共同点
- 提供改变了
url
的方法,不刷新页面 - 监听
url
变化,加载相应内容
- 提供改变了
二、hash模式
1. 原理
使用window.location.hash
属性及窗口的onhashchange
事件,可以实现监听浏览器地址hash
值变化,执行加载相应的内容。
2. 理解要点
-
什么是hash值
hash指的是地址中#号以及后面的字符,也称为散列值。hash也称作锚点,本身是用来做页面跳转定位的。如
http://localhost/index.html#abc
,这里的#abc就是hash; -
hash值不会发送到服务器
hash值是不会随请求发送到服务器端的,所以改变hash,不会重新加载页面;
-
监听 hashchange 事件
监听 window 的 hashchange 事件,当散列值改变时,可以通过 location.hash 来获取和设置hash值;
-
hash值变化反应到地址栏
location.hash值的变化会直接反应到浏览器地址栏;
3. 触发hash.change事件的几种情况
-
触发hash值变化
浏览器地址栏散列值的变化(包括浏览器的前进、后退)会触发window.location.hash值的变化,从而触发onhashchange事件;
-
输入整个url
当浏览器地址栏中URL包含哈希如
http://www.baidu.com/#home
,这时按下输入,浏览器发送http://www.baidu.com/
请求至服务器,请求完毕之后设置散列值为#home,进而触发onhashchange事件; -
输入hash部分
当只改变浏览器地址栏URL的哈希部分,这时按下回车,浏览器不会发送任何请求至服务器,这时发生的只是设置散列值新修改的哈希值,并触发onhashchange事件;
-
点击 a 标签
html中
<a>
标签的属性 href 可以设置为页面的元素ID如#top
,当点击该链接时页面跳转至该id元素所在区域,同时浏览器自动设置window.location.hash
属性,地址栏中的哈希值也会发生改变,并触发onhashchange
事件;
// 设置 url 的 hash,会在当前url后加上'#abc'
window.location.hash='abc';
let hash = window.location.hash //'#abc'
window.addEventListener('hashchange',function(){
// 监听hash变化,点击浏览器的前进后退会触发
})
三、history 模式
1. 原理
history
对象保存了当前窗口访问过的所有页面网址。history
对象发生改变时,只会改变页面的路径,不会刷新页面。- 每当
history
对象出现变化时,就会触发popstate
事件。
2. 理解要点
-
History 对象
HTML5
的History API
为浏览器的全局history
对象增加的扩展方法。一般用来解决ajax请求无法通过回退
按钮回到请求前状态的问题。History
对象保存了当前窗口访问过的所有页面网址。通过history.length
可以得出当前窗口一共访问过几个网址。由于安全原因,浏览器不允许脚本读取这些地址,但是允许在地址之间导航。
浏览器工具栏的“前进”和“后退”按钮,其实就是对
History
对象进行操作。 -
改变url方法
在
history
路由中,我们一定会使用window.history
中的方法,常见的操作有:back()
:后退到上一个路由;forward()
:前进到下一个路由,如果有的话;go(number)
:进入到任意一个路由,正数为前进,负数为后退;pushState(obj, title, url)
:前进到指定的 URL,不刷新页面;replaceState(obj, title, url)
:用 url 替换当前的,不刷新页面;
调用这几种方式时,都会只是修改了当前页面的
URL
,不会刷新页面。如果有面试官问起这个问题“如何仅修改页面的 URL,而不发送请求”,那么答案就是这 5 种方法。但前 3 个方法只是路由历史记录的前进或者后退
,无法跳转到指定的 URL。pushState
和replaceState
是HTML5
新增的history
对象的方法,提供了对历史记录进行修改的功能。页面不会刷新,只是导致 History 对象发生变化,地址栏会有变化。 -
popstate 事件
仅仅调用
pushState()
方法或replaceState()
方法 ,并不会触发该事件。那该怎么办?如果我们能罗列出所有可能改变 history 的途径,然后在这些途径一一进行拦截,不也一样相当于监听了 history 的改变吗?对于一个应用而言,url 的改变只能由以下 3 种途径引起:
- 点击浏览器的前进或者后退按钮;
- 点击 a 标签;
- 在 JS 代码中直接修改路由
第 2 和第 3 种途径可以看成是一种,因为 a 标签的默认事件可以被禁止,进而调用 JS 方法。关键是第 1 种,HTML5 规范中新增了一个
onpopstate
事件,通过它便可以监听到前进或者后退按钮的点击。 -
实现思路
其实现思路也很简单如下图所示:
3. 注意
history
致命的缺点就是当改变页面地址后,强制刷新浏览器时,(如果后端没有做准备的话)会报错,因为刷新是拿当前地址去请求服务器的,如果服务器中没有相应的响应,会出现 404 页面
。
(hash路由没有这个问题)
四、使用场景
- 一般场景下,
hash
和history
都可以,除非你更在意颜值,# 符号夹杂在URL
里看起来确实有些不太美丽。 - 如果不想要很丑的
hash
,我们可以用路由的 history 模式,这种模式充分利用history.pushState API
来完成URL
跳转而无须重新加载页面。不过这种模式要玩好,还需要后台配置支持。 - 所以呢,要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。
五、总结
简介
- 前端路由,是一个前端不同页面的状态管理器,可以不向后台发送请求而直接通过前端技术实现多个页面的效果。
作用
- 保存用户页面状态
- 方便搜索引擎收录
主要介绍两种路由模式:
共同点
- 改变url地址,不刷新页面
- 监听url地址变化,加载相应内容
区别
- hash会在浏览器地址后面增加#号,而history可以自定义地址。
- hash路由不需要服务端的支持,history需要服务端支持,因为hash值不会发给后端,而history模式修改
URL
地址后,再进行刷新会使用该地址发送请求。
详细对比
参考
单页面应用路由实现原理:以 React-Router 为例 · Issue #109 · youngwind/blog · GitHub
细说前端路由的hash模式和 history模式 - 掘金 (juejin.cn)