跳到主要内容

前端路由模式

在前端路由中,hash 模式和 history 模式是两种常见的实现方式。它们用于管理单页应用(SPA)的 URL,使用户可以在不重新加载页面的情况下导航。以下是这两种模式的详细区别和各自的特点:

1. Hash 模式

工作原理

  • URL 中的哈希(#)符号后面的部分不会被发送到服务器,只在客户端处理。
  • 浏览器提供了 window.onhashchange 事件来监听 URL 中哈希部分的变化。
  • 哈希模式依赖于 location.hash 来管理 URL 状态。

特点

  • 兼容性好: 由于哈希部分不影响服务器请求,适用于任何 Web 服务器,无需服务器配置。
  • 简单实现: 只需监听 hashchange 事件即可实现路由切换。
  • SEO 不友好: 哈希 URL 不会被搜索引擎索引,不利于 SEO(搜索引擎优化)。

示例

<ul>
<li><a href="#/">/</a></li>
<li><a href="#/page1">/page1</a></li>
<li><a href="#/page2">/page2</a></li>
</ul>
<div class="content-div"></div>
// 创建路由类
class RouterClass {
constructor() {
this.routes = {}; // 记录路径标识符对应的 cb
this.currentUrl = ''; // 记录hash只为方便执行 cb
window.addEventListener('load', () => this.render());
window.addEventListener('hashchange', () => this.render());
}

// 初始化
static init() {
window.Router = new RouterClass();
}

// 注册路由 和 回调 @param - path - 路径
// @param - cb - 回调
route(path, cb) {
// 将路径及其对应的方法添加到 this.routes 对象中
this.routes[path] = cb || function () {};
}

// 记录当前 hash,执行cb
render() {
this.currentUrl = location.hash.slice(1) || '/';
this.routes[this.currentUrl](); // 默认页面
}
}

// 调用方法,监听 load 和 hashchange 事件
RouterClass.init();
// 过去div 并 给div中添加数据
const ContentDom = document.querySelector('.content-div');
const changeContent = content => (ContentDom.innerHTML = content);

// 调用方法
Router.route('/', () => changeContent('默认页面'));
Router.route('/page1', () => changeContent('page1页面'));
Router.route('/page2', () => changeContent('page2页面'));

2. History 模式

工作原理

  • 利用 HTML5 的 history.pushStatehistory.replaceState API 来管理浏览历史。
  • 可以改变浏览器地址栏中的 URL,但不会触发页面刷新。
  • 使用 popstate 事件来监听浏览器的前进和后退操作。

pushState

假设在http://mozilla.org/foo.html中执行了以下 js 代码:

let stateObj = { foo: 'bar' };
history.pushState(stateObj, 'page 2', 'bar.html');

这将使浏览器地址栏显示为http://mozilla.org/bar.html,但是并不会导致浏览器加载bar.html,甚至不会检查bar.html是否存在。

假设用户又访问了http://google.com,然后点击了返回按钮,此时地址栏现实的是 http://mozilla.org/bar.htmlhistory.state中包含了stateObj的一份拷贝。页面此时展现为bar.html。切页面被重新加载了,所以popstate事件将不会被触发。

如果我们再次点击返回按钮,页面 URL 会变为http://mozilla.org/foo.html,文档对象 document 会触发另外一个 popstate 事件,这一次的事件对象state objectnull

这里也一样,返回并不改变文档的内容,尽管文档在接收 popstate 事件时可能会改变自己的内容,其内容仍与之前的展现一致。

replaceState

replaceState()修改了当前的历史记录项而不是新建一个。 注意这并不会阻止其在全局浏览器历史记录中创建一个新的历史记录项。

假设http://mozilla.org/foo.html执行了如下 JavaScript 代码:

let stateObj = { foo: 'bar' };
history.pushState(stateObj, 'page 2', 'bar.html');

然后,假设http://mozilla.org/bar.html执行了如下 JavaScript:

history.replaceState(stateObj, 'page 3', 'bar2.html');

这将会导致地址栏显示http://mozilla.org/bar2.html,但是浏览器并不会去加载bar2.html 甚至都不需要检查 bar2.html 是否存在。

假设现在用户重新导向到了http://www.microsoft.com,然后点击了回退按钮。这里,地址栏会显示http://mozilla.org/bar2.html。假如用户再次点击回退按钮,地址栏会显示http://mozilla.org/foo.html,完全跳过了bar.html

popstate 事件

每当活动的历史记录项发生变化时, popstate 事件都会被传递给 window 对象。如果当前活动的历史记录项是被 pushState 创建的,或者是由 replaceState 改变的,那么 popstate事件的状态属性 state 会包含一个当前历史记录状态对象的拷贝。

特点

  • 无哈希符号: URL 看起来更干净、专业,例如 http://example.com/home
  • SEO 友好: 这种模式的 URL 可以被搜索引擎索引。
  • 需要服务器配置: 服务器需要配置所有路由指向同一个 HTML 文件,否则会导致 404 错误。

示例

<ul>
<li><a href="/">/</a></li>
<li><a href="/page1">page1</a></li>
<li><a href="/page2">page2</a></li>
</ul>
<div class="content-div"></div>
class RouterClass {
constructor(path) {
this.routes = {};
history.replaceState({ path }, null, path);
this.routes[path] && this.routes[path]();
window.addEventListener('popstate', e => {
const path = e.state && e.state.path;
this.routes[path] && this.routes[path]();
});
}

static init() {
window.Router = new RouterClass(location.pathname);
}
route(path, cb) {
this.routes[path] = cb || function () {};
}

// 触发路由对应回调
go(path) {
history.pushState({ path }, null, path);
this.routes[path] && this.routes[path]();
}
}

RouterClass.init();
const ul = document.querySelector('ul');
const ContentDom = document.querySelector('.content-div');
const changeContent = content => (ContentDom.innerHTML = content);

Router.route('/', () => changeContent('默认页面'));
Router.route('/page1', () => changeContent('page1页面'));
Router.route('/page2', () => changeContent('page2页面'));

ul.addEventListener('click', e => {
if (e.target.tagName === 'A') {
e.preventDefault();
Router.go(e.target.getAttribute('href'));
}
});

3. 对比总结

  • URL 结构:
    • Hash 模式: http://example.com/#/home
    • History 模式: http://example.com/home
  • SEO:
    • Hash 模式: 不友好,搜索引擎无法索引。
    • History 模式: 友好,URL 可以被索引。
  • 服务器配置:
    • Hash 模式: 无需特殊配置。
    • History 模式: 需要服务器配置重定向规则。
  • 兼容性:
    • Hash 模式: 所有浏览器都支持,无需额外配置。
    • History 模式: 需要 HTML5 支持,现代浏览器兼容,但需配置服务器。

选择哪种模式取决于具体需求。如果希望简单实现而无需额外服务器配置,可以选择 hash 模式。如果注重 URL 结构和 SEO,愿意配置服务器,则推荐使用 history 模式。