History API與瀏覽器歷史堆疊管理

2022-02-11 09:39:05 字數 4677 閱讀 5785

移動端開發在某些場景中有著特殊需求,如為了提高使用者體驗和加快響應速度,常常在部分工程採用spa架構。傳統的單頁應用基於url的hash值進行路由,這種實現不存在相容性問題,但是缺點也有--針對不支援onhashchange屬性的ie6-7需要設定定時器不斷檢查hash值改變,效能上並不是很友好。

而如今,在移動端開發中html5規範給我們提供了乙個history介面,使用該介面可以自由操縱歷史記錄。本文並不詳細介紹history介面,而是**history介面如何影響瀏覽器歷史堆疊,並且利用這個規律應用到具體的實際業務中,提出兩種歷史記錄儲存策略,使路由邏輯更清晰,讓spa更容易。

html5 history api包括2個方法:history.pushstate()和history.replacestate(),和1個事件:window.onpopstate。

history.pushstate(stateobject, title, url),包括三個引數。

第乙個引數用於儲存該url對應的狀態物件,該物件可在onpopstate事件中獲取,也可在history物件中獲取。

第二個引數是標題,目前瀏覽器並未實現。

第三個引數則是設定的url。一般設定為相對路徑,如果設定為絕對路徑時需要保證同源。

pushstate函式向瀏覽器的歷史堆疊壓入乙個url為設定值的記錄,並改變歷史堆疊的當前指標至棧頂。

在這裡筆者使用歷史堆疊和當前指標,用以說明瀏覽器對歷史記錄的管理策略。文件中並沒有使用這樣的詞彙,筆者為了更形象的介紹介面對瀏覽器歷史記錄的影響,使用這樣的描述,如有不當之處請及時指出(不過目前以這套模型為基礎的邏輯實現中並未出現悖論)。

該介面與pushstate引數相同,含義也相同。唯一的區別在於replacestate是替換瀏覽器歷史堆疊的當前歷史記錄為設定的url。需要注意的是,replacestate不會改動瀏覽器歷史堆疊的當前指標。

該事件是window的屬性。該事件會在呼叫瀏覽器的前進、後退以及執行history.forward、history.back、和history.go觸發,因為這些操作有乙個共性,即修改了歷史堆疊的當前指標。在不改變document的前提下,一旦當前指標改變則會觸發onpopstate事件。

上文中加粗的「後退」,意味著使用瀏覽器後退按鈕,或者使用手機自帶的返回,再或者使用頁面上提供的後退按鈕。

這樣乙個很細小的需求,但是一旦真正放手去做卻不是那麼容易。僅僅根據history api的2個函式和1個事件去盲目的嘗試實現,這屬於盲人摸象,魯棒性不高。不清楚瀏覽器的歷史記錄管理策略,不了解當前頁面的歷史記錄數量,此種情況若要實現上述場景就有些麻煩。所以在具體動手寫業務**之前,需要搞懂history的pushstate和replacestate具體如何影響歷史記錄棧。

由於瀏覽器並未針對每個頁面的歷史記錄提供具體訪問的介面,因此所有的測試都是黑盒。但是在移動端的中,大都是webkit核心,其webcore的具體實現也都相近,因此該節得出的結論完全可以在移動端使用。

儘管無法訪問當前頁的歷史記錄棧,但是瀏覽器卻提供了history.length屬性,它標明了當前歷史記錄棧的個數。該值會幫助我們更好地分析history api對歷史記錄棧的影響。

上圖為測試例項。其中白色箭頭意味著點選該鏈結並執行pushstate操作(即操作1),黑色箭頭則執行瀏覽器後退,紅色的圓點為歷史記錄棧中的當前指標,而每個項則為歷史記錄棧,歷史記錄的個數則為其子項的數量。

瀏覽器針對每個頁面維護乙個history棧。執行pushstate函式可壓入設定的url至棧頂,同時修改當前指標;

當執行back操作時,history棧大小並不會改變(history.length不變),僅僅移動當前指標的位置;

若當前指標在history棧的中間位置(非棧頂),此時執行pushstate會改變history棧的大小。

總結pushstate的規律,可發現當前指標在history棧頂部時執行pushstate,會增加history棧大小;若current指標不在棧頂則會在當前指標所在位置新增項。執行back操作並不修改history棧大小,因此可以通過back和forward在當前大小的history棧中自由移動。

針對第一種,其實實現最為簡單,因為這完全是由瀏覽器預設控制歷史記錄堆疊,而我們只需在合適的時機呼叫pushstate將url插入到堆疊,然後在onpopstate處理函式中監聽對應的時間即可:

window.addeventlistener('popstate', function (e) else

if(e.state && e.state.indexof('/shop/comment/commentlist.html') !== -1)else

// 後退(前進)至詳情頁,非同步載入資料渲染

if(e.state && e.state.indexof('/shop/item/pictext/') !== -1)else

// 後退(前進)至列表頁,隱藏浮層

if(e.state && e.state.indexof('/search/') !== -1)

});

針對第二種實現,則是本文的重點。畢竟,由瀏覽器預設維護的歷史堆疊在某些業務場景中並不匹配,因此需要開發者自己維護乙個歷史記錄棧。在本次實現中,由於總共涉及4張頁面的顯示,因此我們設定了3層歷史堆疊,這很好理解。

為了構建這樣的歷史記錄棧,在主頁面(即列表頁)中需要額外新增兩條歷史記錄。這是由於預設開啟列表頁時,當前頁面的url已加入歷史記錄棧中,

function push(state)

// 'abc'用於標示初始列表頁

history.replacestate('abc',null,location.pathname + location.search)

// 壓入兩條歷史記錄

push();

push();

這樣,開啟列表頁後就會建立3個歷史記錄,並且這3個歷史記錄的url都為列表頁的url,這與後面的操作並無影響。

在列表頁中開啟詳情頁,需要做額外的處理。由於按照我們設計的歷史記錄棧,第二層應該為詳情頁,而此時在初始化後,歷史記錄棧的當前指標已指向棧頂元素,因此需要將當前指標下移一位。這裡就需要history.back來完成。

$('.item-list').on('click','a',handler);

// 非同步載入詳情資料

var handler = function(e,isscrollxclick);

var isscrollxclick;

/***

*/ var ajaxdetail = function(url,isscrollxclick)

// 非同步觸發

settimeout(function())

// 針對推薦欄的商品,迴圈繫結事件,此處用事件**優化

$('#j_pdslider').on('click','a',function(e));

},error: function(xhr, type)

})};

在此處實現,通過isscrollxclick變數判斷是否點選的是推薦商品,如果不是則需要執行back操作,下移指標。此時指標是指在第二層,但是瀏覽器和第二層歷史記錄的url仍為初始化設定的url,因此需要修改,在這裡非同步修改當前url。

之所以非同步執行replacestate,是由於webkit觸發popstate事件決定的。在**中執行history.back 或者history.forward,並不會立即返回,也不會立即觸發popstate事件。由於沒有閱讀webkit的原始碼,因此無從推測執行back或者forward後具體需要額外做什麼操作,它們之間有著10us級別的間隔,因此此處必須使用settimeout實現非同步改變url。

在具體開發過程中,這個問題困擾著筆者好幾天,終於在一次除錯過程中發現瀏覽器url的變動,才聯想到可能是由事件觸發的時間差導致。

最後一次後退需要回到列表頁,而在初始化階段我們給列表頁設定了state為「abc」,特殊的標示該路由,因此在popstate事件處理中,我們就可以根據該項回到初始頁:

window.addeventlistener('popstate', function (e) else if(e.state && e.state.indexof('abc') !== -1)

});

如果回到初始頁,隱藏浮層,同時在執行2次push操作。根據上節發現的規律,在初始頁執行2次push操作,會在當前指標位置重新新增2個歷史記錄,當前指標指向棧頂元素,歷史記錄棧的數量不變,仍為3。這樣就完成了簡單的由開發者自定義維護歷史堆疊的spa系統。

之所以會寫這篇文章完全是出於偶然,由於實際專案的各種需求我們不應該僅僅將眼光停留在使用api的層面上。另外,在開發過程中遇到難以解決的問題,需要提出各種合理的設想並用詳實的實驗證明,在得到相對應的結論後需要利用該結論去例證其他場景,這樣才能確保解決方案的可靠性。目前網路上或者書籍中並未提供任何手動維護歷史記錄堆疊的方法,也未明確指出history api與瀏覽器歷史記錄之間如何影響,因此本文對於旨在利用history api實現spa的開發者而言還是有些指導意義的。

js與瀏覽器

渲染引擎 語法規則和渲染 負債html和css 瀏覽器如何繪製頁面 js引擎 針對js 2001年ie6首次實現對js引擎的優化和分離 2008年chrome瀏覽器v8引擎把js 直接轉化為機械碼,速度快 後來firefox也推出強大功能的js引擎 其他模組 特點 翻譯一行,執行一行 優點 不翻譯成...

瀏覽器 瀏覽器高效搜尋

一 常見場景 以場景的形式來說明 1 場景1 網路上查詢一本書的 指定文件型別 如pdf 如 搜尋關鍵字 c primer plus 的 pdf 版本 輸入 格式 關鍵字 空格 filetype pdf 例項 c primer plus中文版 filetype pdf關鍵字 filetype 2 場...

常見瀏覽器 瀏覽器核心

常見瀏覽器介紹 瀏覽器是網頁執行的平台,常用的瀏覽器有ie 火狐 firefox 谷歌 chrome safari和opera等。我們平時稱為五大瀏覽器。瀏覽器核心 理解 瀏覽器核心又可以分成兩部分 渲染引擎 layout engineer 或者 rendering engine 和 js 引擎。渲...