在咱們平常的網頁瀏覽中,咱們很是喜歡作一個操做:點擊瀏覽器的前進後退
在Ajax技術出現後,有些時候前進後退就會給開發者帶來困擾,甚至一些開發者試圖去幹掉History
隨着Html5的發展,移動端的興旺,單頁應用出現了,因而History的處理被不得不提上議程了!
要知道,這一直是一項讓人不肯意去碰的巨坑,可是單頁應用卻不得不去解決javascript
首先History的處理邏輯看似簡單,實則複雜,稍不注意就會出問題,咱們這裏來探討下單頁中History的處理規則css
javascript中History的歷史對象包含用戶已經瀏覽的URL信息,這就是咱們傳說中的歷史記錄
咱們通常會用到forward/back兩個方法與一個length接口,或者使用go具體到哪一層html
後面一點,瀏覽器廠商發現History對象確實被管的過緊,因而又釋放了兩個關鍵接口,pushState以及replaceState,用於操做History對象java
因而咱們今天的一個重點即是這裏的pushState以及replaceState,這兩位同窗能夠向History中壓入對象,而且在瀏覽器前進後退時會被觸發jquery
pushState會往History中寫入一個對象,他形成的結果即是
① History length +1
② url 改變
③ 該索引History對應有一個State對象web
這個時候如果點擊瀏覽器的後退,便會觸發popstate事件,將剛剛的存入數據對象讀出,這裏舉一個簡單例子瀏覽器
<html xmlns="http://www.w3.org/1999/xhtml"><head> <title></title> <style type="text/css"> div { margin: 10px; } .msgBtn { margin: 10px; padding: 10px; border: 1px solid black; } </style> <script id="others_zepto_10rc1" type="text/javascript" class="library" src="/js/sandbox/other/zepto.min.js"></script> </head> <body> <div id="msg"> 消息框</div> <br><br> <span class="msgBtn">去第一頁</span> <span class="msgBtn">去第二頁</span> <span class="msgBtn">去第三頁</span> <script src="../../jquery-1.7.1.js" type="text/javascript"></script> <script type="text/javascript"> var _loc = location.href; function showMsg(el, msg) { el.html(msg); } window.addEventListener('popstate', function (e) { if (!e.state) return; showMsg($('#msg'), e.state); }); $('.msgBtn').click(function (e) { var msg = $(e.target).html(); showMsg($('#msg'), msg); history.pushState(msg, msg, _loc + '/' + msg); }); </script> <style></style> <script></script> <!-- Generated by RunJS (Wed May 07 18:05:27 CST 2014) 1ms --></body></html>
http://sandbox.runjs.cn/show/cspv3812app
咱們點擊第一頁時,往History中壓入了數據,而且往裏面寫入了State對象(當前爲msg),而後咱們在瀏覽器後退時便會觸發popstate事件
這個時候咱們的URL已經發生改變,咱們在事件點觸發時便能進行操做了,咱們這裏的操做是改變msg的信息
因此這裏咱們獲得的結果是
① pushState 會改變History
② 每次使用時候會爲該索引的State加入咱們自定義數據
③ 每次History的變化(forward、back、go)皆會致使popstate的觸發,而且將對應索引的State搞出來
④ 每次咱們會根據State的信息還原當前的view,因而用戶點擊後退便有了與瀏覽器後退前進一致的感覺框架
如今咱們有個問題,原來History咱們什麼都不能幹,如今State可存儲容量問題,由於State可存任何東西,不少用戶就會開始亂搞,這個時候其容量是否有限制dom
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title></title> <style type="text/css"> div { margin: 10px; } .msgBtn { margin: 10px; padding: 10px; border: 1px solid black; } </style> <script id="others_zepto_10rc1" type="text/javascript" class="library" src="/js/sandbox/other/zepto.min.js"></script> </head> <body> <div id="msg"> 消息框</div> <br /><br /> <ul id="list"></ul> <span class="msgBtn">去第一頁</span> <span class="msgBtn">去第二頁</span> <span class="msgBtn">去第三頁</span> <script src="../../jquery-1.7.1.js" type="text/javascript"></script> <script type="text/javascript"> var _loc = location.href; var doc = document.body.innerHTML; var list = $('#list'); function showMsg(el, msg) { el.html(msg); } window.addEventListener('popstate', function (e) { if (!e.state) return; showMsg($('#msg'), e.state.msg); console.log(e.state.obj); }); for (var i = 0; i < 100; i++) { var li = $('<li class="msgBtn">' + '當前第' + i + '頁' + '</li>'); list.append(li); } $('.msgBtn').on('click', function (e) { var msg = $(e.target).html(); showMsg($('#msg'), msg); doc = doc + msg; history.pushState({ msg: msg, obj: doc }, msg, _loc + '/' + msg); }); </script> </body> </html>
http://sandbox.runjs.cn/show/69oovy4b
這裏存了一個較大字符串,而且搞了點擊,看了下好像問題不大,因而不予關注了,基礎知識也到此
PS:咱們能夠根據history.state獲取當前的狀態值,若是有的話
在常規的網頁中,首次進入一個網站,此時History length爲1,不通過特殊處理的話,State爲null
一次本標籤連接操做length會加1
在單頁中,基本思路也是如此,不一樣的是,咱們一個個頁面變成,一個頁面上的一個個頁面卡片
咱們如今的頁面跳轉是
A->B->C->D
說白了這個只不過是頁面上的4個dom對象來回的顯示隱藏罷了
因此咱們全部的規則指望的是與History邏輯保持一致,如此惱人的回退問題即可以還給瀏覽器,好比:
A-B-C
如今咱們想從C回到B,這個時候有兩個可能的動做,動做不一樣會形成不一樣的結果
一個是forward B;一個是back B
forward便會造成A-B-C-B的History隊列結果
back的話仍然是A-B-C,並且當前處於B狀態,瀏覽器前進可用
這個事實上與瀏覽器是保持一致的,好比咱們由A進入B後,B頁面有一個link標籤連接到A
這時B返回A產生的結果便與上述相似了
比較噁心的事情每每與不按套路出牌有關,好比總有網站你一旦進去,點擊瀏覽器後退就出不來了,一樣的事情會發生在移動端
好比我如今直接由URL連接進列表頁,那麼此時我點擊瀏覽器是不具有後退操做的,可是咱們傳統的單頁應用頭部都會有一個回退按鈕
此時一剎那便2B了,由於我點擊該回退按鈕勢必是能夠回到index頁面的,因而框架與瀏覽器的History便亂了
這個回退充滿玄機,他是在History小於1的時候的處理邏輯,這裏咱們有些時候不會往History插入新值,因而
瀏覽器看來這個時候是 B,框架的路由倒是B->A,因而咱們點擊A的搜索再次進入列表頁(B),這個時候
框架:B->A->B
瀏覽器:B->B
這個時候咱們優雅的點擊了瀏覽器的回退,B頁面發現本身的History大於1了,因而便執行瀏覽器的回退操做,結果全亂了
固然,通常狀況下,咱們不會像上面那樣作,咱們會在B->A的時候往History中插入數據,讓他們保持一致性
框架:B->A->B
瀏覽器:B->A->B
狀況每每沒有那麼樂觀,更常見的狀況是,如下場景
咱們在訂單填寫頁寫完了訂單,因而點擊確認後便跳到了訂單完成頁,這個時候咱們忽然發現訂單完成頁上面竟然有一個回退按鈕,這個時候的行爲便不是咱們說了算的了,可能發生的場景以下:
① 回到訂單填寫頁(可能已經失效,該行爲最不可能)
② 回到產品搜索頁
③ 回到大首頁
④ 回到訂單列表頁
以上是業務邏輯的需求,可是咱們手賤的狀況點擊了一下瀏覽器自帶的回退,發現尼瑪哥又回來了(回到訂單頁)
因而業務邏輯與瀏覽器邏輯又壞了,此次並且壞的不輕,由於這裏涉及另一個事實!
訂單完成頁是共享的,他至少有三個入口
① 訂單填寫頁
② 用戶訂單列表
③ 複製url新頁面打開(此場景較少)
業務回退不是單純的瀏覽器回退,而且訂單列表與訂單完成還可能不是一個頻道(存在③的問題)
此狀況制約於業務的需求,甚至說業務同事的代碼邏輯能力直接關聯,就一個簡單的訂單完成頁便有不少邏輯
因此實際的狀況是History處理仍然是世界難題
因此現實的狀況是,咱們不會對History作特殊處理
另外一種更加逗比的情形是:
我在A頁面進入B頁面,而後再B頁面很是2B的使用window.location回指A頁面,而A的back按鈕又是使用原生的History.back的話便死循環了
這個場景真實的發生過,咱們當時有一個支付頁面須要進入到禮品卡頁面操做(跨頻道),而後禮品卡成功後直接使用window.loacation回指
支付頁,這個時候支付頁面點擊後退又回到了禮品卡頁面,而禮品卡頁面回退很2B的仍是window.location,因而,結果你們都懂
上面那種場景出現的機率應該說不低,好比咱們還有一個更噁心的場景是在hybrid內嵌時候發生
web頻道頁調native公共組件,因而進入native頁面,最後返回web頻道頁面(這個時候webview中的History空了)
咱們這時點擊頁面卡片的後退極有多是操做window.location,而回跳的頁面若不幸恰好是Historyback
那麼他又會回來了......
好了,今天閒扯了一回History,如果您有任何處理History的方案,請不吝賜教