[聚合文章] HTML5 History API

HTML5 2017-11-28 13 阅读

HTML5 History API是HTML5提供对 history 栈中内容的操作。DOM window 对象通过 history 对象提供了对浏览器历史的访问。它暴露了很多有用的方法和属性,允许你在用户浏览历史中向前和向后跳转。另外也提供了一些很有意思的API。在这篇文章中简单的来了解 history 相关的知识。

History

History 接口允许操作浏览器的曾经在标签页或者框架里访问的会话历史记录。它提供了一些属性和方法。

History 接口不继承于任何属性:

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

History 接口不继承任何方法:

  • History.back() :前往上一页,用户可点击浏览器左上角的返回按钮模拟此方法,等价于 history.go(-1)
  • History.forward() :在浏览器历史记录里前往下一页,用户可点击浏览器左上角的前进按钮模拟此方法,等价于 history.go(1)
  • History.go() :通过当前页面的相对位置从浏览器历史记录(会话记录)加载页面。比如,参数为 -1 的时候为上一页,参数为 1 的时候为下一页。当整数参数超出界限时,例如:如果当前页为第一页,前面已经没有页面了,我传参的值为 -1 ,那么这个方法没有任何效果也不会报错。调用没有参数的 go() 方法或者不是整数的参数时也没有效果
  • History.pushState() :按指定的名称和 URL (如果提供该参数)将数据 push 进会话历史栈,数据被DOM进行不透明处理;你可以指定任何可以被序列化的JavaScript对象
  • History.replaceState() :按指定的数据,名称和 URL (如果提供该参数),更新历史栈上最新的入口。这个数据被DOM进行了不透明处理。你可以指定任何可以被序列化的JavaScript对象

history对象

浏览器窗口有一个 history 对象,用来保存浏览历史。如果当前窗口访问一个网址,那么 history 对象就包括一项,那么 history.length 属性等于 1

history.length // => 1

history 对象提供了一系列方法,允许在浏览历史之间移动:

  • history.back() :移动到上一个访问页面,等同于浏览器的返回按钮
  • history.forward() :移动到下一个访问页面,等同于浏览器的前进按钮
  • history.go() :接受一个整数作为参数,移动到该整数指定的页面,比如 history.go(1) 相当于 history.forward()history.go(-1) 相当于 history.back()

比如你在一个网页中点击一个链接进入到另一个页面,这个时候你在控制台中执行:

history.back()

其作用就相当于点击了后退按钮:

history.forward()

其作用就相当于点击了向前按钮:

如果移动的位置超出了访问历史的边界,那么执行上面的命令,并不会报错,而只是默默的失败。

其中 history.go(0) 相当于刷新当前页面。

常见的“返回上一页”的链接,我们的JavaScript代码可以这样写:

document.getElementById('back').addEventListener('click', function() {
    window.history.back()
})

对应的“向前一页”的链接,就可以这样写:

document.getElementById('forward').addEventListener('click', function() {
    window.history.forward()
})

注意,上述的操作,页面通常是从浏览器缓存中加载,而不是重新要求服务器发送新的请求。

在HTML5中, history 对象新添加了两个方法:

  • history.pushState()
  • history.replaceState()

这两个方法主要用来在浏览历史中添加和修改记录。

if (!!(window.history && history.pushState)) {
    // 支持 History API
} else {
    // 不支持 History API
}

上面代码用来检测浏览器是否支持History API。如果不支持可以考虑其对应的 Polyfill库 History.js 。接下来的内容,假设你的浏览器都支持History API。

先来简单的看 history.pushState() 方法,这个方法接受三个参数:

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

假定当前网址是 w3cplus.com/1.html ,我们使用 pushState() 方法在浏览记录( history 对象)中添加一个新记录。

history.pushState({title: 'w3cplus'}, 'blog', '2.html')

添加上面这个新记录后,浏览器地址栏立刻显示 w3cplus.com/2.html ,但并不会跳转到 2.html ,甚至也不会检查 2.html 是否存在,它只是成为浏览历史中的最新记录。

这个时候,你在地址栏中输入一个新的地址,比如 w3cplus.com/3.html ,然后点击后退按钮,页面的 URL 将显示 w3cplus.com/2.html ;你再点击一次后退按钮, URL 将显示 1.html

总之,pushState方法不会触发页面刷新,只是导致history对象发生变化,地址栏会有反应。

如果 pushStateurl 参数,设置了一个新的锚点值(即 hash ),并不会触发 hashchange 事件。如果设置了一个跨域网址,则会报错。

history.pushState({title: 'twitter'}, 'twitter', 'https://twitter.com/w3cplus')

上面代码中, pushState 想要插入一个跨域的网址,导致报错。这样设计的目的是,防止恶意代码让用户以为他们是在另一个网站上。

history.replaceState() 方法的参数与 history.pushState() 方法一模一样,区别是它修改浏览器历史中当前记录。

假定当前网址是 w3cplus.com

// URL显示为:https://www.w3cplus.com/?page=1
history.pushState({page: 1}, 'title 1', '?page=1')

// URL显示为:https://www.w3cplus.com/?page=2
history.pushState({page: 2}, 'title 2', '?page=2')

// URL显示为:https://www.w3cplus.com/?page=3
history.pushState({page: 3}, 'title 3', '?page=3')

// URL显示为:https://www.w3cplus.com/?page=1
history.back()

// URL显示为:https://www.w3cplus.com
history.back()

// URL显示为:https://www.w3cplus.com/?page=3
history.go(2)

除了上述之外,还有 history.state 属性,该属性返回当前页面的 state 对象。假设你现在是在 www.w3cplus.com 这个网址,先使用 pushState() 方法,给浏览器添加一个新的记录:

history.pushState({page: 1}, 'page 1', 'blog.html?page=1')

和前面介绍的一样, URL 变成了 https://www.w3cplus.com/blog.html?page=1 ,但并不会刷新页面。这个时候,使用 history.state 可以获取到当前页面的 state 对象:

除此之外,每当同一个文档的浏览历史(即 history 对象)发生变化时,就会触发 popstate 事件。需要特别注意的是, 仅仅调用 history.pushState()history.replaceState() 方法时,并不会触发 popstate 事件,只有用户点击浏览器后退按钮和前进按钮,或者使用JavaScript调用 history.back()history.forward()history.go() 方法时才会触发 。另外, popstate 事件只针对同一个文档,如果浏览器历史的切换,导致加载不同的文档,该事件也不会被触发。

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

window.onpopstate = function (event) {
    console.log('location: ' + document.location)
    console.log('state: ' + JSON.stringify(event.state))
}

也可以这样使用:

window.addEventListener('popstate', function(event) {
    console.log('location: ' + document.location)
    console.log('state: ' + JSON.stringify(event.state))   
})

上面代码中的 event.state 就是通过 history.pushState()history.replaceState() 方法,为当前 URL 绑定的 state 对象。这个 state 对象也可以直接通过 history 对象读取。

let currentState = history.state

注意:页面第一次加载的时候,在 load 事件发生后,Chrome和Safari浏览器(Webkit内核)会触发 popstate 事件,而Firefox和IE浏览器不会。

URLSearchParams API

URLSearchParams API 用于处理 URL 的查询字符串,即问号之后的部分。没有部署这个API浏览器,可以使用 url-search-params 这个库

let paramsString = 'q=URLUtils.searchParams&topic=api'
let searchParams = new URLSearchParams(paramsString)

URLSearchParams 此接口不继承任何方法,但其具有以下一些方法,用来操作某个参数。

  • URLSearchParams.has() :返回 Boolean 判断是否存在此搜索参数
  • URLSearchParams.get() :获取指定搜索参数的第一个值
  • URLSearchParams.getAll() :获取指定搜索参数的所有值,返回一个数组
  • URLSearchParams.set() :设置一个搜索参数的新值,假如原来有多个值将删除其他所有的值
  • URLSearchParams.delete() :从搜索参数列表里删除指定的搜索参数及其对应的值
  • URLSearchParams.append() :插入一个指定的键/值对作为新的搜索参数
  • URLSearchParams.toString() :返回搜索参数组成的字符串,可直接使用在 URL

来看一个简单的示例:

let paramsString = 'q=URLUtils.searchParams&topic=api'
let searchParams = new URLSearchParams(paramsString)

console.log(searchParams)               // => URLSearchParams {}

searchParams.has('topic') === true      // => false
searchParams.get('topic') === 'api'     // => false
searchParams.getAll('topic')            // => []
searchParams.get('foo') === ' '         // => false
searchParams.set('topic', 'More webdev')// => undefined
searchParams.delete('topic')            // => undefined
searchParams.append('topic', 'webdev')  // => undefined
searchParams.toString()                 // => q=URLUtils.searchParams&topic=webdev
searchParams.delete('topic')            // => undefined  
searchParams.toString()                 // => q=URLUtils.searchParams

URLSearchParams 还有几个方法,可以用来遍历所有参数:

  • URLSearchParams.keys() :遍历所有参数名
  • URLSearchParams.values() :遍历所有参数值
  • URLSearchParams.entries() :遍历所有参数的键/值对

上面的三个方法返回的都是 Iterator 对象。

let searchParams = new URLSearchParams('key1=value1&key2=value2&key3=value3')

console.log(searchParams)

for (let key of searchParams.keys()) {
    console.log(key)
}

for (let value of searchParams.values()) {
    console.log(value)
}

for (let pair of searchParams.entries()) {
    console.log(pair[0] + ',' + pair[1] + ',' + pair[2])
}

一个实现了 URLSearchParams 的对象可以直接用在 for ... of 结构中,不需要使用 entries()URLSearchParams 实例本身就是 Iterator 对象,与 entries() 方法返回值相同。所以,可以写成下面这样:

for (let p of searchParams) {
    console.log(p)
}

下面是一个替换当前 URL 的例子。

// URL: https://www.w3cplus.com?version=1.0
let params = new URLSearchParams(location.search.slice(1))
params.set('version', 2.0)

window.history.replaceState(
    {},
    '',
    `${location.pathname}?${params}`
)

浏览器 URL 的地址变成: https://www.w3cplus.com?version=2.0

URLSearchParams 实例可以当作 POST 数据发送,所有数据都会 URL 编码:

let params = new URLSearchParams()
params.append('api_key', '1234567890')

fetch('https://example.com/api',{
    methods: 'POST',
    body: params
}).then(...)

DOM的 a 元素节点的 searchParams 属性,就是一个 URLSearchParams 实例:

let a = document.createElement('a')

a.href = 'https://example.com?filter=api'
a.searchParams.get('filter') // => api

URLSearchParams 还可以与 URL 接口结合使用:

let url = new URL(location)
let foo = url.searchParams.get('foo') || 'somedefault'

总结

上面主要了解了有关于HTML5 History API的相关知识点,通过 History 的API可以改变 URL 地址,而且不会发送请求。同时还有 onpopstate 事件。通过这些就能实现前端路由。通过这些知识点的学习,能更好的帮助我们后面理解前端路由打下一个基础。如果你对前端路由相关的知识感兴趣的话,欢迎观注后续的更新。

参考资料

大漠

常用昵称“大漠”,W3CPlus创始人,目前就职于手淘。对HTML5、CSS3和Sass等前端脚本语言有非常深入的认识和丰富的实践经验,尤其专注对CSS3的研究,是国内最早研究和使用CSS3技术的一批人。CSS3、Sass和Drupal中国布道者。2014年出版《 图解CSS3:核心技术与案例实战 》。

如需转载,烦请注明出处: https://www.w3cplus.com/html5/html5-history-api.html

注:本文内容来自互联网,旨在为开发者提供分享、交流的平台。如有涉及文章版权等事宜,请你联系站长进行处理。