????????HTML5 History API提供了一種功能,能讓開發(fā)人員在不刷新整個(gè)頁面的情況下修改站點(diǎn)的URL。這個(gè)功能很有用,例如通過一段JavaScript代碼局部加載頁面的內(nèi)容,你希望通過改變當(dāng)前頁面的URL來反應(yīng)出頁面內(nèi)容的變化,這時(shí)該功能可以派上用場(chǎng)。
????????舉個(gè)例子,當(dāng)用戶從首頁進(jìn)入幫助頁面時(shí),我們通過Ajax來加載幫助頁面的內(nèi)容。然后這個(gè)用戶又轉(zhuǎn)到產(chǎn)品頁面,我們需要再一次通過Ajax請(qǐng)求來替換頁面的內(nèi)容。當(dāng)用戶想分享頁面的URL時(shí),通過History API,我們可以改變頁面的URL來反應(yīng)內(nèi)容的修改,這樣不管是用戶分享還是保存的URL都能和頁面的內(nèi)容對(duì)應(yīng)起來。
基本知識(shí)
????????要查看這個(gè)API提供了哪些功能非常簡單,打開瀏覽器的Developer Tools工具面板,然后在console中輸入history。如果你的瀏覽器支持History API,你將會(huì)看到這個(gè)對(duì)象下面附帶了很多方法。

注意其中的pushState和replaceState這兩個(gè)方法。我們可以在console中進(jìn)行一些簡單的測(cè)試,來看看當(dāng)我們使用這兩個(gè)方法時(shí)URL會(huì)發(fā)生什么變化。稍后我們將分析這兩個(gè)方法中的所有參數(shù),現(xiàn)在我們只需要關(guān)注最后一個(gè)參數(shù):
history.replaceState(null, null, 'hello');
????????上面代碼中的replaceState方法改變了當(dāng)前頁面的URL,在后面添加了一個(gè)'/hello'。不過并沒有發(fā)出任何request請(qǐng)求,當(dāng)前窗口仍然停留在之前的頁面。不過這里有個(gè)問題,當(dāng)你點(diǎn)擊瀏覽器的后退按鈕時(shí),頁面并不會(huì)回退到我們通過replaceState方法修改之前的那個(gè)URL,而是直接回退到了上一個(gè)頁面(即我們進(jìn)入到這個(gè)頁面之前的那個(gè)頁面)。這是因?yàn)?strong>replaceState方法不會(huì)修改瀏覽器的history,它只是簡單地替換了地址欄中的URL。
要解決這個(gè)問題我們需要使用pushState方法:
history.pushState(null, null, 'hello');
????????現(xiàn)在再點(diǎn)擊瀏覽器的后退按鈕,你會(huì)發(fā)現(xiàn)它和你預(yù)想的效果一樣。因?yàn)?strong>pushState方法將我們傳給它的URL添加到瀏覽器的history中,從而改變了瀏覽器的history。假如我們將另外一個(gè)完整的站點(diǎn)URL傳遞給它會(huì)發(fā)生什么情況呢?例如我們?cè)?a target="_blank" rel="nofollow">baidu.com的首頁進(jìn)行測(cè)試,然后在console中輸入下面的內(nèi)容。
history.pushState(null, null, 'https://twitter.com/hello');
????????瀏覽器會(huì)報(bào)錯(cuò)。因?yàn)閭鬟f給pushState方法的URl必須和當(dāng)前頁面的URL屬于同一個(gè)源(即不能跨域),否則會(huì)有很大的安全漏洞,開發(fā)人員可能會(huì)借用該功能來欺騙用戶,讓他們覺得自己是在訪問一個(gè)完全不同的站點(diǎn),而事實(shí)并非如此。
來看看傳遞給pushState方法的所有參數(shù):
history.pushState([data], [title], [url]);
- 第一個(gè)參數(shù)用來傳遞我們需要的數(shù)據(jù),當(dāng)頁面的狀態(tài)發(fā)生變化時(shí)我們可以接收到該數(shù)據(jù)。如用戶點(diǎn)擊瀏覽器的后退和向前按鈕。需要注意的是在Firefox中只允許傳遞最多640K的數(shù)據(jù)。
- 第二個(gè)參數(shù)title是一個(gè)字符串,不過截止到目前,幾乎所有的瀏覽器都忽略該參數(shù)。
- 最后一個(gè)參數(shù)是我們想要替換的URL。
簡單回顧一下
????????這些History API最主要的功能就是不重新加載頁面。以往我們只能通過改變window.location的值來修改當(dāng)前頁面的URL,不過這會(huì)導(dǎo)致整個(gè)頁面被重新加載。如果你修改的只是URL中的hash,則不會(huì)導(dǎo)致頁面被刷新。
????????使用舊的hashbang方法可以改變頁面的URL而不刷新頁面。著名的Twitter就是使用的該方法,不過也廣受詬病,畢竟hash在location中并不被作為一個(gè)真正的資源來對(duì)待。
????????作為History API的早期支持者,Twitter后來拋棄了傳統(tǒng)的hashbang方法。在2012年,Twitter的團(tuán)隊(duì)介紹了他們的新方法,并列出了其中的一些問題同時(shí)還詳細(xì)地介紹了各瀏覽器應(yīng)該如何實(shí)現(xiàn)該規(guī)范。
一個(gè)使用pushState和Ajax的例子
https://css-tricks.com/examples/State/
????????在該示例中,我們希望用戶通過我們的網(wǎng)站找到電影捉鬼敢死隊(duì)(一部美國電影)中的演員。當(dāng)用戶選擇一個(gè)圖片時(shí),我們需要在下方顯示該演員對(duì)應(yīng)的文字描述,同時(shí)給該圖片一個(gè)被選中的效果。當(dāng)點(diǎn)擊后退按鈕時(shí),頁面應(yīng)該切換到上一個(gè)被選中的圖片狀態(tài),同時(shí)圖片下方的文字也要一并切換。當(dāng)點(diǎn)擊前進(jìn)按鈕時(shí)也一樣。
這里有一個(gè)效果圖:

這個(gè)示例的HTML代碼非常簡單:div.gallery中包含了所有的鏈接,每個(gè)鏈接里有一個(gè)圖片。接下來我們放置了一個(gè)空的div.content,用來存放當(dāng)演員圖片被點(diǎn)擊時(shí)顯示在下放的文字。
<div class="gallery">
<a >
<img src="bill.png" alt="Peter" class="peter" data-name="peter">
</a>
<a >
<img src="ray.png" alt="Ray" class="ray" data-name="ray">
</a>
<a >
<img src="egon.png" alt="Egon" class="egon" data-name="egon">
</a>
<a >
<img src="winston.png" alt="Winston" class="winston" data-name="winston">
</a>
</div>
<p class="selected">Ghostbusters</p>
<p class="highlight"></p>
<div class="content"></div>
????????如果沒有JavaScript該頁面仍然可以正常工作,點(diǎn)擊圖片可以跳轉(zhuǎn)到對(duì)應(yīng)的頁面,然后點(diǎn)擊后退按鈕也可以回到之前的頁面。這是為了考慮頁面的可訪問行和優(yōu)雅降級(jí)。
????????接下來我們要添加JavaScript代碼了。我們通過event propagation給div.gallery元素中的每一個(gè)link添加一個(gè)事件處理程序,像這樣:
var container = document.querySelector('.gallery');
container.addEventListener('click', function(e) {
if (e.target != e.currentTarget) {
e.preventDefault();
// e.target is the image inside the link we just clicked.
}
e.stopPropagation();
}, false);
????????在if語句中,我們獲取到被選中圖片的data-name屬性的值,然后將'.html'添加到后面拼成一個(gè)要訪問的頁面地址,并將其作為第三個(gè)參數(shù)傳遞給pushState方法(不過在真實(shí)的例子中我們可能會(huì)在Ajax請(qǐng)求成功之后才會(huì)去修改URL)。
var data = e.target.getAttribute('data-name'),
url = data + ".html";
history.pushState(null, null, url);
// 此處更改當(dāng)前的classes樣式
// 然后使用data變量的值更新
// 并通過Ajax請(qǐng)求.content元素的內(nèi)容
// 最后再更新當(dāng)前文檔的title
(當(dāng)然,此處我們也可以直接使用link的href屬性的值)
????????我將真實(shí)代碼中的內(nèi)容都替換成注釋了,這樣我們可以只關(guān)注pushState方法的使用。
????????現(xiàn)在我們點(diǎn)擊圖片,URL和Ajax請(qǐng)求的內(nèi)容會(huì)被自動(dòng)更新,但是當(dāng)我們點(diǎn)擊后退按鈕時(shí)并不會(huì)回退到之前選中的演員圖片。這里我們還需要在用戶點(diǎn)擊后退和前進(jìn)按鈕時(shí)使用另外一個(gè)Ajax請(qǐng)求來更新內(nèi)容,并再一次使用pushState方法來更新頁面的URL。
????????我們使用pushState方法中的第一個(gè)參數(shù)(其中的state)來保存狀態(tài)信息:
history.pushState(data, null, url);
????????上面代碼中的data參數(shù)在popstate事件觸發(fā)時(shí)可以被獲取到。當(dāng)瀏覽器的后退和前進(jìn)按鈕被點(diǎn)擊時(shí)會(huì)觸發(fā)popstate事件。
window.addEventListener('popstate', function(e) {
// e.state表示上一個(gè)被點(diǎn)擊的圖片的data-attribute
});
我們可以通過該參數(shù)傳遞一些我們需要的信息,例如在該示例中我們將之前選中的捉鬼敢死隊(duì)的演員作為參數(shù)傳遞給requestContent方法,在該方法中,我們使用jQuery的load方法進(jìn)行一次Ajax請(qǐng)求。
function requestContent(file) {
$('.content').load(file + ' .content');
}
window.addEventListener('popstate', function(e) {
var character = e.state;
if (character == null) {
removeCurrentClass();
textWrapper.innerHTML = " ";
content.innerHTML = " ";
document.title = defaultTitle;
} else {
updateText(character);
requestContent(character + ".html");
addCurrentClass(character);
document.title = "Ghostbuster | " + character;
}
});
????????如果用戶點(diǎn)擊了演員Ray的圖片,event listener會(huì)被觸發(fā),然后在pushState事件中保存圖片的data屬性的值。當(dāng)用戶點(diǎn)擊另外一個(gè)圖片,并點(diǎn)擊了瀏覽器的后退按鈕,此時(shí)popstate事件會(huì)被觸發(fā),從而重新加載ray.html頁面。
????????這意味著什么呢?當(dāng)我們點(diǎn)擊一個(gè)演員的圖片然后將被更改的URL分享出去,用戶訪問這個(gè)URL時(shí)對(duì)應(yīng)的HTML文件會(huì)被自動(dòng)加載進(jìn)來。這會(huì)帶來一些更好的用戶體驗(yàn),并保證了URL和頁面內(nèi)容的一致性從而減少了因此而帶給用戶的一些困惑。
????????上面的示例只是簡單地通過jQuery來動(dòng)態(tài)加載內(nèi)容,我們當(dāng)然也可以在pushState方法中傳遞一些更加復(fù)雜的對(duì)象。不過這個(gè)例子已經(jīng)能足夠說明問題并幫助我們開始學(xué)習(xí)如何使用History API的功能。我們先要學(xué)會(huì)走,然后才能跑。