一文读懂前端路由模式history

History对象


在chrome中控制台输入”history”查看对象:

{
    length: 1
    pushState: f(),
    repalceState: f(),
    scrollRestoration: "auto"
    state: null
    __proto__: History
}

History属性


(1)History.length (只读)
返回一个整数,该整数表示会话历史中元素的数目,包括当前加载的页。例如,在一个新的选项卡加载的一个页面中,这个属性返回1。
(2)History.state (只读)
返回一个表示历史堆栈顶部的状态的值。这是一种可以不必等待popstate 事件而查看状态而的方式。
(3)History.scrollRestoration
允许Web应用程序在历史导航上显式地设置默认滚动恢复行为。此属性可以是自动的(auto)或者手动的(manual)。

History方法


(1)History.back()
前往上一页, 用户可点击浏览器左上角的返回按钮模拟此方法. 等价于 history.go(-1)。
注意:当浏览器会话历史记录处于第一页时调用此方法没有效果,而且也不会报错。
(2)History.forward()
在浏览器历史记录里前往下一页,用户可点击浏览器左上角的前进按钮模拟此方法. 等价于 history.go(1).
注意:当浏览器历史栈处于最顶端时( 当前页面处于最后一页时 )调用此方法没有效果也不报错。
(3)History.go(n)
通过当前页面的相对位置从浏览器历史记录( 会话记录 )加载页面。比如:参数为-1的时候为上一页,参数为1的时候为下一页. 当整数参数超出界限时,例如: 如果当前页为第一页,前面已经没有页面了,我传参的值为-1,那么这个方法没有任何效果也不会报错。调用没有参数的 go() 方法或者不是整数的参数时也没有效果。( 这点与支持字符串作为url参数的IE有点不同)。传0会刷新当前页面。
(4)history.pushState(state, title , URL)
添加历史记录中的条目。不会立即加载页面的情况下改变了当前URL地址,往历史记录添加一条条目,除非刷新页面等操作。

三个参数:

  • 状态对象

state是一个JavaScript对象,popstate事件的state属性包含该历史记录条目状态对象的副本。
状态对象可以是能被序列化的任何东西。原因在于Firefox将状态对象保存在用户的磁盘上,以便在用户重启浏览器时使用,我们规定了状态对象在序列化表示后有640k的大小限制。如果你给 pushState() 方法传了一个序列化后大于640k的状态对象,该方法会抛出异常。如果你需要更大的空间,建议使用 sessionStorage 以及 localStorage.

  • 标题

Firefox 目前忽略这个参数,但未来可能会用到。在此处传一个空字符串应该可以安全的防范未来这个方法的更改。或者,你可以为跳转的state传递一个短标题。

  • URL

新的历史URL记录。新URL不必须为绝对路径。如果新URL是相对路径,那么它将被作为相对于当前URL处理。新URL必须与当前URL同源,否则 pushState() 会抛出一个异常。该参数是可选的,缺省为当前URL。

注意: pushState() 绝对不会触发 hashchange 事件,即使新的URL与旧的URL仅哈希不同也是如此。

(5)history.replaceState(state, title , URL)
更改历史记录中的当前条目。不会立即加载页面的情况下改变了当前URL地址,并改变历史记录的当前条目,除非刷新页面等操作。

History相关事件


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

// 绑定事件处理函数. 
window.onpopstate = function(event) {
  alert("location: " + document.location + ", state: " + JSON.stringify(event.state));
};

// 添加并激活一个历史记录条目 http://example.com/example.html?page=1,条目索引为1
history.pushState({page: 1}, "title 1", "?page=1");
// 添加并激活一个历史记录条目 http://example.com/example.html?page=2,条目索引为2
history.pushState({page: 2}, "title 2", "?page=2"); 
// 修改当前激活的历史记录条目 http://ex..?page=2 变为 http://ex..?page=3,条目索引为3
history.replaceState({page: 3}, "title 3", "?page=3"); 

//  弹出 "location: http://example.com/example.html?page=1, state: {"page":1}"
history.back(); 
//  弹出 "location: http://example.com/example.html, state: null
history.back(); 
//  弹出 "location: http://example.com/example.html?page=3, state: {"page":3}
history.go(2);

注意:既然 history.pushState 和 history.replaceState 都不会触发页面的更新,我们就需要手动给 window 对象添加 pushState 和 replaceState 事件,这个很重要!

const listenWrapper = function (type) {
      const _func = history[type];
      return function () {
        console.log(this);
        const func = _func.apply(this, arguments);
        const e = new Event(type);
        e.arguments = arguments;
        window.dispatchEvent(e);
        return func;
      };
    };
    history.pushState = listenWrapper('pushState');
    history.replaceState = listenWrapper('replaceState');
    window.addEventListener('pushState', function (e) {
      console.log(e)
    });

解释如下:

  1. 在pushState执行的时候创建自定义事件
  2. 在pushSatate外部写自定义事件的监听事件
  3. 在pushState执行的时候执行自定义事件

获取当前状态


页面加载时,或许会有个非null的状态对象。这是有可能发生的,举个例子,假如页面(通过pushState() 或 replaceState() 方法)设置了状态对象而后用户重启了浏览器。那么当页面重新加载时,页面会接收一个onload事件,但没有 popstate 事件。然而,假如你读取了history.state属性,你将会得到如同popstate 被触发时能得到的状态对象。

你可以读取当前历史记录项的状态对象state,而不必等待popstate 事件。

思路



.

Demo示例


监听点击事件禁止默认跳转操作,手动利用history实现一套跳转逻辑,根据location.pathname渲染界面。


<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
    <meta http-equiv="X-UA-Compatible" content="ie=edge"/>
    <title>Document</title>
  </head>
  <body>
    <ul>
      <li>
        <a href="/a">a</a>
      </li>
      <li>
        <a href="/b">b</a>
      </li>
      <li>
        <a href="/c">c</a>
      </li>
    </ul>
    <div id="view"></div>

    <script>
      var view = null;
      // 页面加载完不会触发 hashchange,这里主动触发一次 hashchange 事件,
      // 该事件快于onLoad,所以需要在这里操作
      window.addEventListener('DOMContentLoaded', function () {
        view = document.querySelector('#view');
        document
          .querySelectorAll('a[href]')
          .forEach(e => e.addEventListener('click', function (_e) {
            _e.preventDefault();
            history.pushState(null, '', e.getAttribute('href'));
            viewChange();
          }));

        viewChange();
      });
      // 监听路由变化
      window.addEventListener('popstate', viewChange);

      // 渲染视图
      function viewChange() {
        switch (location.pathname) {
          case '/b':
            view.innerHTML = 'b';
            break;
          case '/c':
            view.innerHTML = 'c';
            break;
          default:
            view.innerHTML = 'a';
            break;
        }
      }
</script>
  </body>
</html>

注意:该方法不支持本地运行,只能线上运作或者启动服务器查看效果。

标签: