HTML5 history API
標簽(空格分隔): JavaScript
DOM
window對象通過history對象提供了對瀏覽器歷史的訪問。它暴露了很多有用的方法和屬性,允許你在用戶瀏覽歷史中向前和向后跳轉,同時,從HTML5開始提供了對history棧中內容的操作。
一、 在history中跳轉
使用back(),forward(),go()方法來完成在用戶歷史記錄中向后和向前跳轉。
// 在history中向后跳轉
window.history.back();
window.history.go(-1);
// 向前跳轉
window.history.forward();
window.history.go(1);
// 當前頁
window.history.go(0);
二、添加和修改歷史記錄中的條目 (pushState()和replaceState())
HTML5引入了history.pushState()和history.replaceState()方法,他們分別可以添加和修改歷史記錄條目。這些方法通常與window.onpopstate配合使用。
使用history.pushState()可以改變referrer(引用),它在用戶發送XMLHttpRequest請求時在HTTP頭部使用,改變state后創建的XMLHttpRequset對象的referrer都會被改變。因為referrer是標識創建XMLHttpRequest對象時this所代表的window對象中document的URL。
pushState()方法示例
假設有以下幾個文件
假設在http://demo.com/foo.html中執行了一下js代碼:
var stateObj = { foo: 'bar' };
window.history.pushState(stateObj, 'page 2', 'bar.html');
這將使瀏覽器地址欄顯示為http://demo.com/bar.html,但並不會導致瀏覽器加載bar.html,甚至不會檢查bar.html是否存在。如下圖所示:

現在假設用戶又訪問了下面的鏈接http://demo.com/other.html,然后點擊了返回按鈕。此時,地址欄將顯示http://demo.com/bar.html,同時頁面會觸發popstate事件,事件對象state中包含了stateObj的一份拷貝。頁面本身與foo.html一樣,盡管其在popstate事件中可能會修改自身內容。
如果我們再次點擊返回按鈕,頁面URL會變為http://demo.com/foo.html,文檔對象document會觸發另外一個popstate事件,這一次的事件對象state object為null。這里也一樣,返回並不改變文檔的內容,盡管文檔在接受popstate事件時可能會改變自己的內容,其內容仍與之前的展現一致。
pushState()方法
pushState()需要三個參數:一個狀態對象,一個標題(目前被忽略)和一個URL(可選的)。
-
狀態對象
狀態對象state是一個js對象,通過pushState()創建新的歷史記錄條目。無論什么時候用戶導航到新的狀態,popstate事件就會被觸發,且該事件的state屬性包含該歷史記錄條目狀態對象的副本。
狀態對象可以是能被序列化的任何東西。原因在於火狐將狀態對象保存在用戶的磁盤上,以便用戶重啟瀏覽器時使用,我們規定了狀態對象在序列化表示后有640k的大小限制。如果你給pushState()方法傳了一個序列化后大於640k狀態對象,該方法會拋出異常。如果你需要更大的空間,建議使用sessionStorage以及localStorage。
-
標題
火狐目前忽略這個參數,但未來可能會用到。傳遞一個空字符串在這里是安全的,而在將來這是不安全的。二選一的話,你可以為跳轉的state傳遞一個短標題。
-
URL
該參數定義了新的歷史URL記錄。注意,調用pushState()后瀏覽器並不會立即加載這個URL,但可能會在稍后某些情況下加載這個URL,比如在用戶重新打開瀏覽器時。新URL不必須為絕對路徑。如果新URL是相對路徑那么它將被作為相對於當前URL處理。新URL必須與當前URL同源,否則pushState()會拋出一個異常。該參數是可選的,缺省為當前URL。
某種意義上,調用pushState()與設置widnow.location = '#ff'類似,二者都會在當前頁面創建並激活新的歷史記錄。但pushState()有以下幾條優點:
- 新的URL可以是與當前URL同源的任意URL。而設置
window.location僅當你只修改了哈希值時才保持同一個document。 - 如果需要,你可以不必改變URL。而設置
window.location = '#foo';在當前哈希不是#foo的情況下,僅僅是新建了一個新的歷史記錄選項。 - 你可以為新的歷史記錄項關聯任意數據。而基於哈希值的方式,則必須將所有相關數據編碼到一個短字符串里。
- 假如
標題在之后會被瀏覽器用到,那么這個數據是可以被使用的(哈希則不能)。
注意pushState()絕對不會觸發hashchange事件,即是是新的URL與舊的URL僅哈希不同也不會觸發。
在XUL文檔中,它創建指定的XUL元素。
在其它文檔中,它創建一個命名空間為URI為null的元素。
replaceState()方法
history.replaceState()的使用與history.pushState()非常相似,區別在於replaceState()是修改了當前的歷史記錄項而不是新建一個。注意這並不會阻止其在全局瀏覽器歷史記錄中創建一個新的歷史記錄項。
replaceState()的使用場景在於為了響應用戶操作,你想要更新狀態對象state或者當前歷史記錄的URL。
replaceState()示例
假設http://demo.com/foo.html執行了如下js代碼:
var stateObj = { foo: 'bar' };
window.history.pushState(stateObj, 'page 2', 'bar.html');
假設接着執行了如下代碼:
window.history.replaceState(stateObj, 'page 3', 'bar2.html');
這將會導致地址欄顯示http://demo.com/bar2.html,但是瀏覽器並不會去加載bar2.html甚至不需要檢查bar2.html是否存在。

假設現在用戶重新導向了http://demo.com/other.html,然后點擊了回退按鈕,這里地址欄會顯示http://demo.com/bar2.html,再次點擊回退,地址欄顯示foo.html完全跳過了bar.html
popstate事件
每當活動的歷史記錄項發生變化時,popstate事件都會被傳遞給window對象。如果當前活動的歷史記錄項是被pushState創建的,或者是由replaceState改變的,那么popstate事件的狀態屬性state會包含一個當前歷史記錄狀態對象的拷貝。
獲取當前狀態
頁面加載時,或許會有個非null的狀態對象。這是有可能發生的,你可以讀取當前歷史記錄項的狀態對象state,而不必等待popstate事件,只需要這樣使用history.state屬性:
var currentState = history.state;
三、window.onpopstate
window.onpopstate是popstate事件在window對象上的事件處理程序。
每當處於激活狀態的歷史記錄條目發生變化時,popstate事件就會在對應window對象上觸發。如果當前處於激活狀態的歷史記錄條目事由history.pushState()方法創建,或者由history.replaceState()方法修改過的,則popstate事件對象的state屬性包含了這個歷史記錄條目的state對象的一個拷貝。
調用history.pushState()或者history.replaceState()不會觸發popstate事件。popstate事件只會在瀏覽器某些行為下觸發,比如后退、前進或者在js中調用back()等的方法。
當網頁加載時,各瀏覽器對popstate事件是否觸發有不同的表現,Chrome和Safari會觸發popstate事件,而FireFox不會。
onpopstate語法
window.onpopstate = funcRef;
// funcRef是個函數名
popdtate事件
假如當前網頁地址為http://demo.com/example.html,則運行下述代碼后:
window.onpopstate = function(event) {
alert("location: " + document.location + ", state: " + JSON.stringify(event.state));
};
//綁定事件處理函數.
history.pushState({page: 1}, "title 1", "?page=1"); //添加並激活一個歷史記錄條目 http://demo.com/example.html?page=1,條目索引為1
history.pushState({page: 2}, "title 2", "?page=2"); //添加並激活一個歷史記錄條目 http://demo.com/example.html?page=2,條目索引為2
history.replaceState({page: 3}, "title 3", "?page=3"); //修改當前激活的歷史記錄條目 http://de..?page=2 變為 http://de..?page=3,條目索引為3
history.back(); // 彈出 "location: http://demo.com/example.html?page=1, state: {"page":1}"
history.back(); // 彈出 "location: http://demo.com/example.html, state: null
history.go(2); // 彈出 "location: http://demo.com/example.html?page=3, state: {"page":3}
即便進入了那些非pushState和replaceState方法作用過的(比如http://demo.com/example.html)沒有state對象關聯的那些網頁, popstate事件也仍然會被觸發。
初始打開頁面:

back一次:

back二次:

四、兼容性
Desktop
| 特性 | Chrome | Edge | FireFox | IE | Opera | Safari |
|---|---|---|---|---|---|---|
| replaceState, pushState | 5 | Yes | 4.0 | 10 | 11.50 | 5.0 |
| history.state | 18 | Yes | 4.0 | 10 | 11.50 | 6.0 |
五、實例
參見《ajax與HTML5 history pushState/replaceState實例》
Ajax可以實現頁面的無刷新操作——優點;但是,也會造成另外的問題,無法前進與后退。
當執行Ajax操作的時候,往瀏覽器history中塞入一個地址(使用pushState)(這是無刷新的);於是,返回的時候,通過URL或其他傳參,我們就可以還原到Ajax之前的模樣。
效果如圖所示(源碼地址):

