不知你有没有发现,像Github、百度、微博等这些大站,已经再也不使用普通的a标签作跳转了。他们大多使用Ajax请求替代了a标签的默认跳转,而后使用HTML5的新API修改了Url,你能够在F12的Network面板里发现这个秘密。
这项技术并无特别标准的学名,你们都称呼为Pjax,意为PushState + Ajax。这并不彻底准确,由于还有Hash + Ajax等方法,但为了方便,咱们下文仍是统称为Pjax。javascript
Pjax是一个优秀的解决方案,你有足够多的理由来使用它:java
Pjax的原理十分简单。
1. 拦截a标签的默认跳转动做。
2. 使用Ajax请求新页面。
3. 将返回的Html替换到页面中。
4. 使用HTML5的History API或者Url的Hash修改Url。git
咱们来看看HTML5在History里增长了什么:github
history.pushState(state, title, url)
pushState方法会将当前的url添加到历史记录中,而后修改当前url为新url。请注意,这个方法只会修改地址栏的Url显示,但并不会发出任何请求。咱们正是基于此特性来实现Pjax。它有3个参数:web
history.replaceState(state, title, url)
replaceState方法与pushState大同小异,区别只在于pushState会将当前url添加到历史记录,以后再修改url,而replaceState只是修改url,不添加历史记录。浏览器
window.onpopstate 事件
通常来讲,每当url变更时,popstate事件都会被触发。但如果调用pushState来修改url,该事件则不会触发,所以,咱们能够把它用做浏览器的前进后退事件。该事件有一个参数,就是上文pushState方法的第一个参数state。闭包
这里咱们以daipig为例,打开daipig,地址栏是http://www.daipig.com 。接下来打开F12 Console,输入:函数
history.pushState({ a: 1, b: 2 }, null, "http://www.daipig.com/abcdefg");
能够发现,url已经变成咱们输入的url了,但页面并无刷新,也没有发出任何请求。如今再输入history.state,就能够看到咱们刚刚传过来的第一个参数state了。
这时点击后退,url会回到www.daipig.com,一样是没有刷新。只不事后退的时候实际上是触发了window.onpopstate事件的。动画
详细文档能够查阅MDN: https://developer.mozilla.org/zh-CN/docs/DOM/Manipulating_the_browser_...网站
Pjax的原理上文已经讲了,并不复杂。我实现了一个比较粗糙的Pjax库,已经能知足很多需求,若是你有兴趣,能够上Github帮忙完善一下代码。地址是:https://github.com/Coffcer/coffce-pjax 。
完整的代码见Github,这里咱们只谈须要注意的一些地方。
要实现Pjax,不免就会有匹配选择器的需求。你须要判断当前点击的元素,是否匹配指定选择器。这里我给出一个兼容至IE8的解决方法:
// 判断element是否匹配选择器selector function matchSelector(element, selector) { var match = document.documentElement.webkitMatchesSelector || document.documentElement.mozMatchesSelector || document.documentElement.msMatchesSelector || // 兼容IE8及如下浏览器 function(selector, element) { // 这是一个好方法,惋惜IE8连indexOf都不支持 // return Array.prototype.indexOf.call(document.querySelectorAll(selector), this) !== -1; if (element.tagName === selector.toUpperCase()) return true; var elements = document.querySelectorAll(selector), length = elements.length; while (length--) { if (elements[length] === this) return true; } return false; }; // 重写函数自身,使用闭包keep住match函数,不用每次都判断兼容 matchSelector = function(element, selector) { return match.call(element, selector); }; return matchSelector(element, selector); }
// 判断element是否匹配选择器selector function matchSelector(element, selector) { var match = document.documentElement.webkitMatchesSelector || document.documentElement.mozMatchesSelector || document.documentElement.msMatchesSelector || // 兼容IE8及如下浏览器 function(selector, element) { // 这是一个好方法,惋惜IE8连indexOf都不支持 // return Array.prototype.indexOf.call(document.querySelectorAll(selector), this) !== -1; if (element.tagName === selector.toUpperCase()) return true; var elements = document.querySelectorAll(selector), length = elements.length; while (length--) { if (elements[length] === this) return true; } return false; }; // 重写函数自身,使用闭包keep住match函数,不用每次都判断兼容 matchSelector = function(element, selector) { return match.call(element, selector); }; return matchSelector(element, selector); }
// 验证一下
matchSelector(document.getElementById("abc"), "#abc"); // true
matchSelector(document.querySelector("a"), "p"); // false
在现代浏览器上,优先使用原生的matchesSelector方法来判断,在IE8及如下的浏览器里,循环document.querySelector的结果集,依次对比。
这个方法利用了闭包,而后重写自身,只有在第一次调用时须要判断加哪一个前缀执行哪一个方法,其后都是调用了闭包的match函数。
IE6到IE9是不支持pushState的,要修改Url,只能利用Url的Hash,也便是#号。
你能够随意找个网站试一下,在url后面加上#号和任意内容,页面并不会刷新。此时点击后退也只会回到上一条#号,一样不会刷新。
那么咱们只需把pushState(新url)换成localtion.hash = 新url,把onpopstate事件换成onhashchange事件就能够兼容IE了。
QQ音乐,网易云音乐等就是使用这种方式。
我简单实现了一个比较粗糙的Pjax库,地址是:https://github.com/Coffcer/coffce-pjax ,欢迎PR和Star。