回到列表

HTML5 History & Location & Vue-router简单实现

history

为了可以方便地操作、访问浏览器的历史,浏览器提供了window.history对象,通过调用gobackforward等方法实现浏览器的前进后退。

HTML5新增内容

在最新的HTML5标准中,可以操作history栈中的内容。

pushState

在传统的web访问体验中,点击一个新链接会表现为页面的全量加载。但是在HTML5的加持下,可以利用pushState实现只修改URL而不全量加载(例如各种各样的单页应用SPA—Single Page Application),局部更新页面dom加快了访问速度、改善了web体验。

pushState使用如下,data为存放到history栈的数据,title是网页的标题,url是浏览器地址栏需要展示的地址。使用pushState跳转的页面会添加历史。

history.pushState(data, title, url)

注意:

  1. 经测试chrome/ff下title参数设置无效,需要使用document.title = xxx进行修改。
  2. pushState传入的url需同域,否则浏览器会抛出错误。 pushState error

replaceState

修改当前历史,不添加新历史。使用同pushState

popstate

当浏览历史发生改变时,会触发popstate事件。如果是pushState/replaceState创建的历史,popstate触发时可以获取到pushState/replaceState调用时的第一个参数data的数据拷贝。下面是一个简单的例子,模拟了pushStatepopstate的使用。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <button id="forward">前进</button>
  <button id="backward">后退</button>
  <script>
    let count = 1;
    document.getElementById('forward').addEventListener('click', () => {
      history.pushState({ count }, 'count: ' + count, '?count=' + count)
      count += 1
    })
    document.getElementById('backward').addEventListener('click', () => {
      if (count === 1) return;
      count -= 1;
      history.back()
    })
    window.addEventListener('popstate', (event) => {
      console.log(event)
    })
  </script>
</body>
</html>

popstate的数据可以直接从回调的事件中获取event.state。当然,也可以直接调用history.state获取当前历史中存放的数据。

location

谈到history必然离不开location,这边简单介绍一下location。

类型说明

// location的TS定义
interface Location {
  /**
   * Returns a DOMStringList object listing the origins of the ancestor browsing contexts, from the parent browsing context to the top-level browsing context.
   */
  readonly ancestorOrigins: DOMStringList;
  /**
   * Returns the Location object's URL's fragment (includes leading "#" if non-empty).
   *
   * Can be set, to navigate to the same URL with a changed fragment (ignores leading "#").
   */
  hash: string;
  /**
   * Returns the Location object's URL's host and port (if different from the default port for the scheme).
   *
   * Can be set, to navigate to the same URL with a changed host and port.
   */
  host: string;
  /**
   * Returns the Location object's URL's host.
   *
   * Can be set, to navigate to the same URL with a changed host.
   */
  hostname: string;
  /**
   * Returns the Location object's URL.
   *
   * Can be set, to navigate to the given URL.
   */
  href: string;
  toString(): string;
  /**
   * Returns the Location object's URL's origin.
   */
  readonly origin: string;
  /**
   * Returns the Location object's URL's path.
   *
   * Can be set, to navigate to the same URL with a changed path.
   */
  pathname: string;
  /**
   * Returns the Location object's URL's port.
   *
   * Can be set, to navigate to the same URL with a changed port.
   */
  port: string;
  /**
   * Returns the Location object's URL's scheme.
   *
   * Can be set, to navigate to the same URL with a changed scheme.
   */
  protocol: string;
  /**
   * Returns the Location object's URL's query (includes leading "?" if non-empty).
   *
   * Can be set, to navigate to the same URL with a changed query (ignores leading "?").
   */
  search: string;
  /**
   * Navigates to the given URL.
   */
  assign(url: string): void;
  /**
   * Reloads the current page.
   */
  reload(): void;
  /** @deprecated */
  reload(forcedReload: boolean): void;
  /**
   * Removes the current page from the session history and navigates to the given URL.
   */
  replace(url: string): void;
}

location中属性说明

下面举MDN上面的例子介绍location的属性。

http://example.com:8888/foo/bar?q=baz#bang对应location中的各个部分。

属性 内容 说明
href http://example.com:8888/foo/bar?q=baz#bang
origin http://example.com:8888 protocal + hostname + port
protocal http:
host example.com:8888
hostname example.com
port 8888
pathname /foo/bar
search ?q=baz
hash #bang

location中还有一个只读属性ancestorOrigins,在iframe中可获取外部父页面的域名信息(可用来判断当前文档在iframe中)。

location中方法使用

如果不需要HTML5 History存储对象,直接使用location的API也可实现页面历史操作。

并且,location使用时没有同域的限制。

  1. assign

    location.assign(url)作用等同于location = url(也可以location.href = urllocation.hash = xxx等直接赋值)。

  2. reload

    页面刷新。

  3. replace

    当前页面地址重定向访问。

Vue-router

最近在看Vue-router的实现代码,文章最后就简单介绍一下路由导航的实现。

初始化

vue项目使用Vue-router时,都会使用new VueRouter({ routes })的形式构建出可供Vue实例读取的Router对象。

new操作相应的源码就是Link

路由模式

Vue-router有三种路由模式: historyhashabstract

  1. history

    这个模式使用history.pushState作为历史切换的方式,URL比较正常例如http://example.com/dir/subdir

    对应源码是HTML5History类

    但是使用时需要注意的是,因为浏览器url访问的规则,例如http://example.com/dir/subdir会访问服务器路径/dir/subdir的文件(subdir.htmlsubdir/index.html或同路径服务等等),对于单页应用来说这个路径并没有相应的文件。所以需要在服务端配置路径访问404时返回单页应用的index.html文件。

  2. hash

    这个模式使用locationhash作为单页应用各个路由的访问路径(例如http://example.com/dir/#/spaRoute)。比较简单,不需要服务端额外的404配置。

    对应的源码是HashHistory类。浏览器路由事件的监听使用了popstatehashchange(支持HTML5 History就用popstate,否则使用hashchange)。

  3. abstract

    这个模式通常给nodejs服务端使用(比如服务端渲染SSR),可以模拟浏览器端的历史切换。如果Vue-router运行时的环境不支持浏览器的API,会直接设置成abstract模式。

    对应的源码是AbstractHistory类

路由动作

在上面的三种模式中,路由对象都继承了基类History并模拟实现了类似浏览器location API的gopushreplace方法。

同时,historyhash模式下监听了浏览器的popstatehashchange等事件,在浏览器前进/后退操作时更新SPA。