详解vue 单页应用(spa)前端路由实现原理


Posted in Javascript onApril 04, 2018

写在前面:通常 SPA 中前端路由有2种实现方式:

  1. window.history
  2. location.hash

下面就来介绍下这两种方式具体怎么实现的

一.history

1.history基本介绍

window.history 对象包含浏览器的历史,window.history 对象在编写时可不使用 window 这个前缀。history是实现SPA前端路由是一种主流方法,它有几个原始方法:

  1. history.back() - 与在浏览器点击后退按钮相同
  2. history.forward() - 与在浏览器中点击按钮向前相同
  3. history.go(n) - 接受一个整数作为参数,移动到该整数指定的页面,比如go(1)相当于forward(),go(-1)相当于back(),go(0)相当于刷新当前页面
  4. 如果移动的位置超出了访问历史的边界,以上三个方法并不报错,而是静默失败

在HTML5,history对象提出了 pushState() 方法和 replaceState() 方法,这两个方法可以用来向历史栈中添加数据,就好像 url 变化了一样(过去只有 url 变化历史栈才会变化),这样就可以很好的模拟浏览历史和前进后退了,现在的前端路由也是基于这个原理实现的。

2.history.pushState

pushState(stateObj, title, url) 方法向历史栈中写入数据,其第一个参数是要写入的数据对象(不大于640kB),第二个参数是页面的 title, 第三个参数是 url (相对路径)。

  1. stateObj :一个与指定网址相关的状态对象,popstate事件触发时,该对象会传入回调函数。如果不需要这个对象,此处可以填null。
  2. title:新页面的标题,但是所有浏览器目前都忽略这个值,因此这里可以填null。
  3. url:新的网址,必须与当前页面处在同一个域。浏览器的地址栏将显示这个网址。

关于pushState,有几个值得注意的地方:

pushState方法不会触发页面刷新,只是导致history对象发生变化,地址栏会有反应,只有当触发前进后退等事件(back()和forward()等)时浏览器才会刷新

这里的 url 是受到同源策略限制的,防止恶意脚本模仿其他网站 url 用来欺骗用户,所以当违背同源策略时将会报错

3.history.replaceState

replaceState(stateObj, title, url) 和pushState的区别就在于它不是写入而是替换修改浏览历史中当前纪录,其余和 pushState一模一样

4.popstate事件

定义:每当同一个文档的浏览历史(即history对象)出现变化时,就会触发popstate事件。

注意:仅仅调用pushState方法或replaceState方法 ,并不会触发该事件,只有用户点击浏览器倒退按钮和前进按钮,或者使用JavaScript调用back、forward、go方法时才会触发。另外,该事件只针对同一个文档,如果浏览历史的切换,导致加载不同的文档,该事件也不会触发。

用法:使用的时候,可以为popstate事件指定回调函数。这个回调函数的参数是一个event事件对象,它的state属性指向pushState和replaceState方法为当前URL所提供的状态对象(即这两个方法的第一个参数)。

5.history实现spa前端路由代码

<a class="api a">a.html</a>
<a class="api b">b.html</a>
 // 注册路由
 document.querySelectorAll('.api').forEach(item => {
  item.addEventListener('click', e => {
  e.preventDefault();
  let link = item.textContent;
  if (!!(window.history && history.pushState)) {
   // 支持History API
   window.history.pushState({name: 'api'}, link, link);
  } else {
   // 不支持,可使用一些Polyfill库来实现
  }
  }, false)
 });

 // 监听路由
 window.addEventListener('popstate', e => {
  console.log({
  location: location.href,
  state: e.state
  })
 }, false)

popstate监听函数里打印的e.state便是history.pushState()里传入的第一个参数,在这里即为{name: 'api'}

二.Hash

1.Hash基本介绍

url 中可以带有一个 hash http://localhost:9000/#/a.html

window 对象中有一个事件是 onhashchange,以下几种情况都会触发这个事件:

  1. 直接更改浏览器地址,在最后面增加或改变#hash;
  2. 通过改变location.href或location.hash的值;
  3. 通过触发点击带锚点的链接;
  4. 浏览器前进后退可能导致hash的变化,前提是两个网页地址中的hash值不同。

2.Hash实现spa前端路由代码

// 注册路由
 document.querySelectorAll('.api').forEach(item => {
  item.addEventListener('click', e => {
  e.preventDefault();
  let link = item.textContent;
  location.hash = link;
  }, false)
 });

 // 监听路由
 window.addEventListener('hashchange', e => {
  console.log({
  location: location.href,
  hash: location.hash
  })
 }, false)

hash模式与history模式,这两种模式都是通过浏览器接口实现的,除此之外vue-router还为非浏览器环境准备了一个abstract模式,其原理为用一个数组stack模拟出浏览器历史记录栈的功能。当然,以上只是一些核心逻辑,为保证系统的鲁棒性源码中还有大量的辅助逻辑,也很值得学习。

三、两种模式比较

  • pushState设置的新URL可以是与当前URL同源的任意URL;而hash只可修改#后面的部分,故只可设置与当前同文档的URL
  • pushState设置的新URL可以与当前URL一模一样,这样也会把记录添加到栈中;而hash设置的新值必须与原来不一样才会触发记录添加到栈中
  • pushState通过stateObject可以添加任意类型的数据到记录中;而hash只可添加短字符串
  • pushState可额外设置title属性供后续使用

四、history模式的一个问题

我们知道对于单页应用来讲,理想的使用场景是仅在进入应用时加载index.html,后续在的网络操作通过Ajax完成,不会根据URL重新请求页面,但是难免遇到特殊情况,比如用户直接在地址栏中输入并回车,浏览器重启重新加载应用等。
hash模式仅改变hash部分的内容,而hash部分是不会包含在HTTP请求中的:

http://oursite.com/#/user/id // 如重新请求只会发送http://oursite.com/

故在hash模式下遇到根据URL请求页面的情况不会有问题。

而history模式则会将URL修改得就和正常请求后端的URL一样

http://oursite.com/user/id

在此情况下重新向后端发送请求,如后端没有配置对应/user/id的路由处理,则会返回404错误。

官方推荐的解决办法是在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。同时这么做以后,服务器就不再返回 404 错误页面,因为对于所有路径都会返回 index.html 文件。为了避免这种情况,在 Vue 应用里面覆盖所有的路由情况,然后在给出一个 404 页面。或者,如果是用 Node.js 作后台,可以使用服务端的路由来匹配 URL,当没有匹配到路由的时候返回 404,从而实现 fallback。

参考资料:

1、浏览器History API :https://developer.mozilla.org/zh-CN/docs/Web/API/History_API

2、解决History模式访问404的方案:https://router.vuejs.org/zh/guide/essentials/history-mode.html#%E5%90%8E%E7%AB%AF%E9%85%8D%E7%BD%AE%E4%BE%8B%E5%AD%90

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
js获取url中&quot;?&quot;后面的字串方法
May 15 Javascript
JavaScript中的toDateString()方法使用详解
Jun 12 Javascript
JS实现横向与竖向两个选项卡Tab联动的方法
Sep 27 Javascript
基于JS实现的笛卡尔乘积之商品发布
May 13 Javascript
Angular2中Bootstrap界面库ng-bootstrap详解
Oct 18 Javascript
Javascript中call,apply,bind方法的详解与总结
Dec 12 Javascript
Vue filters过滤器的使用方法
Jul 14 Javascript
BetterScroll 在移动端滚动场景的应用
Sep 18 Javascript
解决VUE框架 导致绑定事件的阻止冒泡失效问题
Feb 24 Javascript
原生JS实现的跳一跳小游戏完整实例
Jan 27 Javascript
世界上最短的数字判断js代码
Sep 09 Javascript
vue 使用原生组件上传图片的实例
Sep 08 Javascript
vue cli升级webapck4总结
Apr 04 #Javascript
JavaScript实现短暂提示框功能
Apr 04 #Javascript
Vue filter介绍及详细使用
Apr 04 #Javascript
在Vue项目中引入腾讯验证码服务的教程
Apr 03 #Javascript
详解如何用babel转换es6的class语法
Apr 03 #Javascript
为vue-router懒加载时下载js的过程中添加loading提示避免无响应问题
Apr 03 #Javascript
新版vue-cli模板下本地开发环境使用node服务器跨域的方法
Apr 03 #Javascript
You might like
隐藏Nginx或Apache以及PHP的版本号的方法
2016/01/03 PHP
[原创]PHP简单开启curl的方法(测试可行)
2016/01/11 PHP
使用js获取QueryString的方法小结
2010/02/28 Javascript
JS 文件传参及处理技巧分析
2010/05/13 Javascript
javascript:FF/Chrome与IE动态加载元素的区别说明
2014/01/26 Javascript
JQuery异步加载无限下拉框级联功能实现示例
2014/02/19 Javascript
Jquery 返回json数据在IE浏览器中提示下载的问题
2014/05/18 Javascript
JS实现网页标题随机显示名人名言的方法
2015/11/03 Javascript
jQuery的ready方法实现原理分析
2016/10/26 Javascript
微信小程序 PHP生成带参数二维码
2017/02/21 Javascript
微信小程序日期时间选择器使用方法
2018/02/01 Javascript
利用SpringMVC过滤器解决vue跨域请求的问题
2018/02/10 Javascript
基于vue中解决v-for使用报红并出现警告的问题
2018/03/03 Javascript
Vuex的基本概念、项目搭建以及入坑点
2018/11/04 Javascript
跨域请求两种方法 jsonp和cors的实现
2018/11/11 Javascript
layui实现显示数据表格、搜索和修改功能示例
2020/06/03 Javascript
Python中无限元素列表的实现方法
2014/08/18 Python
用Python进行基础的函数式编程的教程
2015/03/31 Python
django之session与分页(实例讲解)
2017/11/13 Python
详解python运行三种方式
2019/05/13 Python
实例详解python函数的对象、函数嵌套、名称空间和作用域
2019/05/31 Python
Python socket聊天脚本代码实例
2020/01/02 Python
Python列表去重复项的N种方法(实例代码)
2020/05/12 Python
Ajax的优点和缺点
2014/11/21 面试题
消防器材管理制度
2014/01/28 职场文书
机关门卫制度
2014/02/01 职场文书
大学生实习鉴定评语
2014/04/25 职场文书
班主任工作经验交流材料
2014/05/13 职场文书
建筑学专业自荐书
2014/07/09 职场文书
重阳节活动总结
2014/08/27 职场文书
干部个人考察材料
2014/12/24 职场文书
云冈石窟导游词
2015/02/04 职场文书
寻找成龙观后感
2015/06/12 职场文书
Nginx中break与last的区别详析
2021/03/31 Servers
MySQL 主从复制数据不一致的解决方法
2022/03/18 MySQL
Linux中一对多配置日志服务器的详细步骤
2022/07/23 Servers