接上一篇history源碼解析-管理會話歷史記錄,本篇教你手寫history
,重在理解其原理。javascript
history
是一個JavaScript庫,可以讓你在JavaScript運行的任何地方輕鬆管理會話歷史記錄html
history
是由Facebook維護的,react-router
依賴於history
,區別於瀏覽器的window.history
,history
是包含window.history
的,讓開發者能夠在任何環境都能使用history
的api(例如Node
、React Native
等)。本文中history
指倉庫的對象,window.history
指瀏覽器的對象。java
本篇讀後感分爲五部分,分別爲前言、使用、原理、上手、總結,推薦順序閱讀哈哈。react
附上地址git
<!DOCTYPE html>
<html>
<head>
<script src="history.js"></script>
<script> var createHistory = History.createBrowserHistory var page = 0 // createHistory建立所須要的history對象 var h = createHistory() // h.block觸發在地址欄改變以前,用於告知用戶地址欄即將改變 h.block(function (location, action) { return 'Are you sure you want to go to ' + location.path + '?' }) // h.listen監聽當前地址欄的改變 h.listen(function (location) { console.log(location, 'lis-1') }) </script>
</head>
<body>
<p>Use the two buttons below to test normal transitions.</p>
<p>
<!-- h.push用於跳轉 -->
<button onclick="page++; h.push('/' + page, { page: page })">history.push</button>
<button onclick="h.goBack()">history.goBack</button>
</p>
</body>
</html>
複製代碼
history
用法:github
block
用於地址改變以前的截取;listener
用於監聽地址欄的改變;push
添加新歷史記錄;replace
替換當前歷史記錄;go(n)
跳轉到某條歷史記錄;goBack
返回上一條歷史記錄。解釋:api
push
、replace
、go
、goBack
:倉庫history
的方法pushState
、replaceState
:window.history
的方法,用於修改window.history
歷史記錄popstate
:監聽歷史記錄的改變window.addEventListener('popstate', callback)
forceNextPop
:自定義變量,用於判斷是否跳過彈出框allKeys
:自定義變量,它跟歷史記錄是同步的。每當修改歷史記錄,都會維護這個數組,用於當彈出框點擊取消時,能夠返回到上次歷史記錄go(toIndex - fromIndex)
:彈出框取消時,返回上一次歷史記錄當活動歷史記錄條目更改時,將觸發popstate事件。須要注意的是調用history.pushState()或history.replaceState()不會觸發popstate事件。只有在作出瀏覽器動做時,纔會觸發該事件,如用戶點擊瀏覽器的回退按鈕(或者在Javascript代碼中調用history.back())數組
路線1(push
和replace
):瀏覽器
push
;window.history.pushState
添加歷史記錄,把key
存儲到window.history
中(注意這個時候不會觸發popstate
監聽函數);allKeys
,添加key
到allKeys
數組。線路2(go
和goBack
):react-router
go
;window.history
,地址改變;popstate
歷史記錄監聽函數(若是綁定了popstate
監聽函數);history
(保證history
是最新的信息,例如history.location
是當前地址信息)。toIndex
:跳轉前的地址(取history.location
的值,由於此時的history.location
還沒有更新是舊值);fromIndex
:當前地址的key
;go
方法跳回去上次歷史記錄。這基本是history
的原理了,應該會有些同窗存在疑惑,調用彈出框這個能夠放在調用go
以前,一樣能達到效果,並且代碼會更加簡潔且不須要維護allKeys
這個數組。我以前也有這個疑問,但仔細想一想,go
函數並不包含全部歷史記錄改變的操做,若是用戶左滑動返回上一個頁面呢,那樣就達不到效果了。因此必須在監聽歷史記錄改變後,才能觸發彈出框,當點擊彈出框的取消按鈕後,只能採用維護allKeys
數組的方式來返回上一頁。
代碼都有註釋,100多行代碼模仿history
寫了個簡易閹割版,目的是爲了瞭解history
的原理,應該很容易就看懂的。
(function(w){
let History = {
createBrowserHistory
}
function createBrowserHistory(){
// key
function createKey() {
return Math.random().toString(36).substr(2, 6);
}
// 獲取地址信息
function getDOMLocation(historyState = {}) {
const { key, state } = historyState || {};
const { pathname, search, hash } = window.location;
return {pathname, search, hash, key};
}
// location地址信息
let initialLocation = getDOMLocation()
// 初始化allKeys
let allKeys = [initialLocation.key]
// listen數組
let listener = []
// 監聽
function listen(fn){
listener.push(fn)
checkDOMListeners()
}
// 只能添加一個監聽歷史條目改變的函數
let isListener = false
function checkDOMListeners(){
if (!isListener) {
isListener = true
window.addEventListener('popstate', handlePop)
}
}
// 跳過block。由於當點擊彈出框的取消後,會執行go,而後會再一次執行handlePop函數,這次要跳過
let forceNextPop = false
// 監聽歷史條目改變
function handlePop(event){
let location = getDOMLocation(event.state)
if (forceNextPop) {
forceNextPop = false
} else {
// 彈出框
let isComfirm = prompt && window.confirm(prompt(window.location)) && true
if (isComfirm) {
// 肯定
// 更新history
Object.assign(history, {location, length: history.length})
} else {
// 取消
// 獲取當前的history.key和上一次的location.key比較,而後進行回跳
let toIndex = allKeys.indexOf(history.location.key)
toIndex = toIndex === -1 ? 0 : toIndex
let fromIndex = allKeys.indexOf(location.key)
fromIndex = fromIndex === -1 ? 0 : fromIndex
// 差值
let delta = toIndex - fromIndex
// 差值爲0不跳
if (delta) {
forceNextPop = true;
go(delta);
}
}
}
}
// 截取函數
let prompt = null
function block(fn){
prompt = fn
}
// push
function push(href){
let isComfirm = prompt && window.confirm(prompt(window.location)) && true
if (isComfirm) {
let key = createKey()
// 更新allKeys數組
allKeys.push(key)
// 更新歷史條目
w.history.pushState({key}, null, href)
// 獲取當前最新的location信息
let location = getDOMLocation({key})
// 更新history
Object.assign(history, {location, length: history.length})
}
}
// go
function go(n){
w.history.go(n)
}
// goBack
function goBack(){
go(-1);
}
let history = {
length: w.history.length,
listen,
block,
push,
go,
goBack,
location: initialLocation
}
return history
}
w.History = History
})(window)
複製代碼
學代碼必須手寫,學英語必須開口,學習必須主動!