起因 History back(), forward(), go(), length pushState(), replaceState() pjax 参考 起因 起因是这样的,在尝试前后端分离的这条道路上,我自己也在不断摸索,感觉要把大部分的坑都踩踩了。目前我用的技术是: webpack 自动构建 AMD 模块化 js Sass 预处理 CSS 使用前端模板引擎 handlebars 解决动态操作将 html 拼接在 js 中的问题 但最近写了一个项目类似知乎这样的多页网站。前端 url 的处理让我觉得不够优雅。我使用的是 hash 的方式处理动态 url 的,为此我专门在知乎上提了一个问题:前端如何处理动态url? 这里我将问题描述如下: 前后端彻底分离的情况下,页面跳转页全部由前端控制。那么如何更好的处理动态url地址? 例如本问题的url为 https://www.zhihu.com/question/38802932 这肯定是用后台路由处理的url 纯前端怎么处理?用hash吗,如下: https://www.zhihu.com/question#38802932 那如果本页跳转,只改变hash的话,页面不会刷新。 使用location.reload()倒是可以解决。 但总觉得这样处理不够优雅。大家在工作中是如何处理此类场景的?还是用传统的后台路由来提供动态url? 感谢郑海波和剧中人的热心回答。都提到了history对象中的pushState,这是我第一次接触到这方面的内容(顿时觉得自己真是才疏学浅)。 同时也有人提到了pjax,这个就是pushState+Ajax的封装,也很有意思。 下面就来研究和实践一下吧。 History window对象通过history对象提供对浏览器历史记录的访问能力。它暴露了一些非常有用的方法和属性,让你在历史记录中自由前进和后退,而在 HTML5 中,更可以操纵历史记录中的数据。 back(), forward(), go(), length 浏览器的历史记录就好像一个栈,最新的在最上面,较早之前看过的在下面。 如下图,Chrome的历史记录: 下面介绍怎么在这些历史记录中跳转,但要注意,上图中的浏览器历史记录和本文说的 history 还不太同。 back() 在历史记录中后退 history.back(); forward() 在历史记录中前进 history.forward(); go() 移动到指定的历史记录点 history.go(-1); 通过指定一个相对于当前页面位置的数值,你可以使用go()方法从当前会话的历史记录中加载页面(当前页面位置索引值为0,上一页就是-1,下一页为1)。 go()不填参数或参数为go(0)时,页面会刷新,即history.go()或history.go(0)相当于location.reload() length length为history的属性,显示history长度。 本节在线demo见:History & pjax demo 源代码: 经过亲自测试,history对象只记录同一个 tab 页内的历史。如果是在新窗口打开的,则无效。如:在a标签中添加target="_blank",或按住ctrl点击,这类场景下,在新的tab页中,history对象也是新的。 且history对象记录的信息与是否同源也无关,所以唯一要满足的就是同一个标签页。 pushState(), replaceState() HTML5 引进了history.pushState()方法和history.replaceState()方法,它们允许你逐条地添加和修改历史记录条目,能够在不加载新页面的情况下没改变浏览器的URL。这些方法可以协同window.onpopstate事件一起工作。 使用history.pushState()会改变referrer的值,而在你调用方法后创建的 XMLHttpRequest 对象会在 HTTP 请求头中使用这个值。referrer的值则是创建 XMLHttpRequest 对象时所处的窗口的 URL。 pushState(any data, string title, [string url]) 第一个参数为history对象的state属性值,可以放任意数据,记录历史状态。第二个参数是新状态的标题,目前浏览器基本不支持。第三个参数为可选的相对url。 执行pushState后,可以在不加载新页面的情况下,更改url。同时history栈中新增一条数据。 例如,我们有这样一段代码: <button id="push1">pushState()</button> document.querySelector('#push1').addEventListener('click', function() { history.pushState('abc','pushStatePageTitle','pushState.html'); document.querySelector('#length').innerHTML = history.length;//重新读取历史长度 }); 当点击按钮的时候,页面不会刷新,但url地址的最后已经变为pushState.html。这一点非常像hash的作用,但比hash更优雅。 replaceState(any data, string title, [string url]) 与pushState()类似,只是在history栈中不是新增记录,而是替换一条记录。 需要注意的是:pushState()和replaceState()方法存在安全方面的限制,本地测试是无效的,会报错,可以简单放到任何服务端测试,或者使用http-server开启简单服务器,通过访问localhost来查看效果。 本节demo见:History & pjax demo - pushState pjax 现在再看本文一开始提出的问题,如何让前端优雅的控制 url,这里就可以考虑 pjax 技术了。我们把 pushState + ajax 进行封装,合起来简称为 pjax。虽然不是什么新的技术,但概念已然不同。 如果不使用 pjax。我们依然可以使用hash来实现文本开始的需求。但会不利于 SEO,看着也不够优雅。 Pjax的原理十分简单。 拦截 a 标签的默认跳转动作或某些按钮的点击事件。 使用 Ajax 请求新页面。 将返回的 Html 替换到页面中。 使用 HTML5 的pushState()修改Url。 个人理解3中也可以仅仅请求数据,再由浏览器渲染。 每当同一个文档的浏览历史(即history对象)出现变化时,会触发window.onpopstate事件。 window.onpopstate = function(event) { console.log(event.state); console.log(location); }; 这样在用户点击前进后退时也可以很好的监听url,来做相应的页面渲染。 若用户刷新了页面,但没有相应的页面资源,这时页面就会显示不存在。所以我认为较好的方法是在写pushState()第三个参数的时候,写为?a=1这样的参数形式。History.js 也是这么写的。但是这样应该会多一次请求。也许使用 nodeJS 作为中间层会好一些吧。 对于上述的探索,不知道是不是我还不够深入,总觉得还是不够完美。 参考 MDN History MDN 操纵浏览器的历史记录 pjax 是如何工作的? 知乎 PJAX的实现与应用 小胡子哥 URL的井号-阮一峰 history对象 JavaScript 标准参考教程(alpha) 阮一峰 Pjax(pushState and Ajax) 黯羽轻扬 操纵历史,利用HTML5 History API实现无刷新跳转 蓝飞 前端:将网站打造成单页面应用SPA(一) Coffce coffce-pjax History.js defunkt/jquery-pjax GitHub welefen/pjax