Hash 模式?history 模式?

分享

前言

这次的 why what or how 主题:单页应用的 router 模式,HashHistory 是什么?

router 作为单页应用中的重要组成,有两种模式供我们选择。那么,这两种模式有何区别,各自的优势又在哪里?

想要深入对比两种模式的区别,我们首先要知道什么是路由?

什么是路由?

路由定义如下:路由是 URL 的路径,也就是 URL 中的 path 部分。一个路由对应一个具体资源,可能是文件,也可能是数据。

在单页应用出现之前,路由已在服务端被广泛使用,那么前端何时开始有了路由?

伴随着浏览器性能的提升、网速加快以及 Ajax 技术的成熟,部分前端程序员发现,使用 JavaScript 在前端维护模板,再用 Ajax 从服务端获取数据,就能将模板和数据拼成动态页面呈现给用户。随着这项技术日渐成熟,一个问题逐渐显现出来:如果 JavaScript 端维护了几个不同模板,用于呈现不同状态,该如何维护这些状态?

路由!服务器就是用路由区分不同资源,只需要把路由的概念引入前端开发,用路由来标志这些状态!

那么如何引入?或者说如何实现?

模拟

路由的样子:一个形如 <path>?<query> 的字符串。

那么该如何存储这个路由呢?页面生成之初,我们所拥有的只有 URL。如果一开始就需要确定页面的状态,那就只能用这个 URL

首先分析一下 URL 的组成:

<protocol>://<token>@<host>:<port>/<path>?<query>#<hash>

除去服务器需要使用的,能使用的仅剩下了 hash

虽然在浏览器中 hash 为一个锚点值,但却拥有以下特性:

  1. hash 值改变会记录历史记录。
  2. hash 值未匹配到元素时,页面并不会发生滚动。
  3. hash 值为一个任意的字符串。
  4. hash 的改变并不会让浏览器刷新页面。

那么,现在再来看一下前端路由所需要的特性:

  1. 需要记录历史。
  2. 一个类似 /a/b/c 的字符串。
  3. 路由改变不需要浏览器重新获取资源(也就是不刷新页面)。

这两者似乎是生来就该呆在一起,简直是完美契合。

有了思路,那我们使用 hash 来模拟一个简单的前端路由,如下:

https://test.com/app#/page/123

# 号前是需要请求的服务器路由,# 号后是前端路由。

看起来一切都挺完美?服务器浏览器各自处理各自的部分,JavaScript 中包含了所有的页面模板,以及路由映射表。但这就是最终的版本了吗?

是的!至少在 pushStatereplaceState 出现之前是。那么,这两个 API 效果如何?为何会有这两个 API

History API

先来看看官方定义:

API 作用
pushState 向浏览器的历史记录中添加一条记录,同时将地址栏改为相应的地址,但并不会导致浏览器刷新。
replaceState 直接替换地址栏的链接,不会产生新的历史,同样不会导致浏览器刷新。

需要注意的是,这两个 API 仅能改变 URL<path>?<query> 部分,并不能改变其余部分。

看起来挺符合前端路由的概念的,甚至可以说为了配合前端路由诞生了这两个 API。但是问题真的解决了吗?

问题隐藏在用户主动刷新上。虽然这两个 API 屏蔽了浏览器的刷新,但无法控制用户主动触发的刷新。思考以下场景:

  1. 用户打开 https://test.com/app
  2. 用户点击按钮导航到 https://test.com/app/page/123。由于是 API 控制,不会导致浏览器发出对应地址的请求。
  3. 用户主动刷新了页面。
  4. 浏览器发出 https://test.com/app/page/123 的请求,但服务器并没有对应的资源,导致 404

那么,如何解决?由于问题发生在服务器,只能在服务器上处理:使用 nginx 重定向,或让 https://test.com/app/page/123 返回和 https://test.com/app 一样的结果。那么其他页面怎么办?单页应用可不止有两个页面!写正则过滤请求!

优缺点

在说明优缺点时,把使用 hash 实现前端路由称为 hash 模式,把使用 History API 称为 history 模式。

hash 模式

使用 hash 模拟路由,URL 格式如下:

<protocol>://<token>@<host>:<port>/<path>?<query>#<path>?<query>

优点:

  1. 职责明确,各部分处理各部分的事,互不干扰。
  2. 服务器不需要额外支持。
  3. JavaScript 获取简单。

缺点:

  1. 带有 #/ 不精简,hash 部分的分享可能有问题。

history 模式

直接使用 URL<path>?<query>,格式与 URL 一致:

<protocol>://<token>@<host>:<port>/<path>?<query>#<hash>

优点:

  1. URL 简单,分享容易。
  2. hash 可以匹配 ID

缺点:

  1. 服务器需要支持。
  2. 职责不明确,项目废弃后还需要去服务器上移除相应的过滤,会埋下一些隐藏的 bug
  3. 如果有二级目录存在,请求过滤会比较复杂。

小结

个人喜欢使用 hash 模式,原因如下:

  1. 开发简单。
  2. 现在分享基本上使用二维码,不用直接分享链接。
  3. 不会污染服务器。
  4. 锚点?什么是锚点?我不认识。

最后,照例提几个问题:

  1. 什么是路由?
  2. 前端路由存在的意义是什么?
  3. hash 模式与 history 模式的区别?

阅读更多

JavaScript 对象 & 原型

前言 这次的 why what or how 主题:JavaScript 对象 & 原型。 此类文章在百度上一搜一大把,其实不用再写了。但是本着把这个问题解释得清清楚楚、明明白白的想法,还是开始写了。 原因如下: 1. 面试宝典类文章,放张图片一糊弄,让人觉得自己理解了。 2. 解释类文章,告诉你一堆语法,对语法一顿解释,告诉你就是这样的,还是没说清楚。 3. 很少有文章单独解释这个点!但这个点是基础!真的很重要! 所以,本篇文章想说一说对象 & 原型。但为了确保能顺利理解,请先看完 JS 变量存储?栈 & 堆?NONONO!。因为该篇文章会从变量存储的角度解释 JavaScript 对象 & 原型,请确保看完,再看这篇文章。

By Breeze

浏览器下的 Event Loop

前言 JavaScript 以单线程的形式运行在宿主环境下,并采用回调的形式来解决异步任务。 为什么是单线程? JavaScript 最开始出现,是为了给 Web 页面增添一些动态效果,那么就避免不了获取页面上的元素信息。如果 JavaScript 以多线程的形式运行在浏览器内,两个线程内的 JavaScript 同时获取或修改某个页面上的元素,那么浏览器该让哪个 JavaScript 线程拥有获取或修改该元素的权限呢?由于元素信息会经常发生变化,那么,又该如何同步各个线程内所保存的元素信息呢? 所以,综合以上问题,JavaScript 是单线程的原因就显而易见了。单线程执行时,对于元素信息的引用在同一时间仅可能只有一个,那么以上所有问题都不存在了。 什么是异步任务? 任何代码在执行时,都会碰到一些需要大量时间运算或等待的代码。在浏览器环境下,常见的就是 HTTP 任务,比如资源加载(图片的 onload 事件)、Ajax 请求(XMLHttpRequest 的 onload 事件),还有页面元素的点击事件以及定时器等。 以上任务都极其耗时,而且会受环境影响。

By Breeze

node 下的 Event Loop

前言 通过浏览器下的 Event Loop,可以得知 JavaScript 是一门事件驱动的语言,JavaScript 主线程通过不断调用事件队列中的事件来完成异步任务。那么,Node.js 下是否也是如此? Node.js 下的事件队列 在 Node.js 官网有这样一篇文章:The Node.js Event Loop, Timers, and process.nextTick()。 该文章主要讲述了 Node.js 中如何处理以及实现 Event Loop。 主要包含以下内容: 事件队列 不同于浏览器,Node.js 下有 6 个事件处理阶段,其执行顺序和队列名称如下: ┌───────────────────────────┐ ┌─>│ timers │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ pending callbacks │ │ └─────────────┬─────────────┘ │

By Breeze

什么是 HTML 5?

前言 作为程序员,技术的落实与巩固是必要的,因此想到写个系列,名为 why what or how,每篇文章试图解释清楚一个问题。 这次的 why what or how 主题:现在几乎所有人都知道了 HTML5,那么 H5 相比于 HTML4 到底有什么区别呢? 升级版?标准版! HTML5 作为 HTML 标准的第 5 版,于 2014 年发布。相信关注过 HTML5 发展史的朋友都知道,该版本是 WHATWG 和 W3C 握手言和后诞生的,是 W3C 组织与浏览器厂商相互妥协的结果。其中的绝大部分规范都由 WHATWG 组织制定,之后由 W3C

By Breeze