Window.history是一個只讀屬性,用來獲取History 對象的引用,History 對象提供了操做瀏覽器會話歷史(瀏覽器地址欄中訪問的頁面,以及當前頁面中經過框架加載的頁面)的接口,容許你在用戶瀏覽歷史中向前和向後跳轉。javascript
Window.history是一個只讀屬性,用來獲取History 對象的引用,History 對象提供了操做瀏覽器會話歷史(瀏覽器地址欄中訪問的頁面,以及當前頁面中經過框架加載的頁面)的接口,容許你在用戶瀏覽歷史中向前和向後跳轉。html
同時,從HTML5開始提供了對history棧中內容的操做。 History 對象是 window 對象的一部分,可經過 window.history 屬性對其進行訪問。vue
屬性 | 描述 |
---|---|
length | 返回瀏覽器歷史列表中的 URL 數量。 |
state | 返回一個表示歷史堆棧頂部的狀態的值。這是一種能夠沒必要等待popstate 事件而查看狀態而的方式 |
方法 | 屬性 |
---|---|
back() | 加載 history 列表中的前一個 URL。 |
forward() | 加載 history 列表中的下一個 URL。 |
go() | 加載 history 列表中的某個具體頁面。 |
History 對象最初設計來表示窗口的瀏覽歷史。但出於隱私方面的緣由,History 對象再也不容許腳本訪問已經訪問過的實際 URL。惟一保持使用的功能只有 back()、forward() 和 go() 方法。html5
<html lang="en">
<head>
<meta charset="UTF-8">
<title>history test</title>
<script type="text/javascript" src="jquery.min.js"></script>
</head>
<body>
<button onclick="count()">count</button>
<button onclick="doForWard()">forward</button>
<button onclick="doBack()">back</button>
<script> var index=1; function count(){ console.log(`window.history.length = ${history.length}`); } function doForWard(){ history.forward(); } function doBack(){ history.back(); } </script>
</body>
</html>
複製代碼
使用ajax,能夠實現不須要刷新整個頁面就能夠進行局部頁面的更新。這樣能夠開發交互性很強的富客戶端程序,減小網絡傳輸的內容。但長期以來存在一個問題,就是沒法利用瀏覽器自己提供的前進和後退按鈕進行操做。好比在頁面執行某個動做,該動做利用ajax請求到服務器獲取數據,更新了當前頁面的某些內容,這時想回到操做前的界面,用戶就會習慣點擊瀏覽器的後退按鈕,實際這裏是無效的。 HTML5引入了histtory.pushState()和history.replaceState()這兩個方法,它們會更新history對象的內容。同時,結合window.onpostate事件,就能夠解決這個問題。java
方法 | 屬性 |
---|---|
pushState() | 按指定的名稱和URL(若是提供該參數)將數據push進會話歷史棧 |
replaceState() | 按指定的數據,名稱和URL(若是提供該參數),更新歷史棧上最新的入口 |
<html lang="en">
<head>
<meta charset="UTF-8">
<title>history test</title>
<script type="text/javascript" src="jquery.min.js"></script>
</head>
<body>
<button onclick="count()">count</button>
<button onclick="doForWard()">forward</button>
<button onclick="doBack()">back</button>
<button onclick="doPushState()">pushState</button>
<button onclick="doReplaceState()">replaceState</button>
<script> var index=1; function count(){ console.log(`window.history.length = ${history.length}`); } function doForWard(){ history.forward(); } function doBack(){ history.back(); } function doPushState(){ history.pushState({page:index++}, null,'?page='+index); } function doReplaceState(){ history.replaceState({page:index++}, null,'?page='+index); } </script>
</body>
</html>
複製代碼
pushState接受3個參數 1)第一個參數是個js對象,能夠聽任何的內容,能夠在onpostate事件中獲取到便於作相應的處理。jquery
2)第二個參數是個字符串,目前任何瀏覽器都沒有實現,但將來可能會用到,能夠傳個空串。ajax
3)第三個參數是個字符串,就是保存到history中的url。vue-router
調用 pushState() 後瀏覽器並不會當即加載這個URL,但可能會在稍後某些狀況下加載這個URL,好比在用戶從新打開瀏覽器時。新URL沒必要須爲絕對路徑。若是新URL是相對路徑,那麼它將被做爲相對於當前URL處理。新URL必須與當前URL同源,不然 pushState() 會拋出一個異常。該參數是可選的,缺省爲當前URL。 pushState的做用是修改history歷史,當調用方法時,將當前url存入history棧中,並在地址欄中顯示參數中的url,可是並不會加載新的url。 replaceState的參數和pushState相同,可是調用時並不會將舊url存入history,而是將history中的url替換爲參數中的url。chrome
當活動歷史記錄條目更改時,將觸發popstate事件。若是被激活的歷史記錄條目是經過對history.pushState()的調用建立的,或者受到對history.replaceState()的調用的影響。 popstate事件的state屬性包含歷史條目的狀態對象的副本,也是pushState以及replaceState函數跳轉到當前頁面時傳遞的第一個參數。瀏覽器
popstate事件在瀏覽器前進、後退時會觸發,配合pushState和replaceState,能夠解決使用ajax的頁面前進後退的問題。
具體解決方案是在觸發ajax事件時同時調用pushState事件修改url信息,當popState 被觸發時,同時調用history事件,根據當前url的不一樣,調用ajax事件恢復對應的狀態。
history.pushState(null, null, document.URL);
window.addEventListener('popstate', function () {
history.pushState(null, null, document.URL);
});
複製代碼
這段代碼會把當前url先加入url歷史中,此時history棧頂端有兩個相同的url,當點擊後退事件時,退回到前一個url,可是頁面會監聽後退事件並再次push一個相同的url進入棧中。 對於用戶來講點擊後退按鈕後,url永遠不變,頁面也沒有從新加載(出於道德倫理公約,此方法慎用)
chrome在考慮新的history實現 baijiahao.baidu.com/s?id=163318…
history是HTML5新方法,在舊版本的IE瀏覽器中有兼容性問題,而且history在更改url的時候不會發送請求刷新頁面, 若是是想在切換url的同時加載url,則可使用location.assign和location.replace方法
路由要實現的功能:
hash
/** * hash 模式 */
function Route(){
this.routes = {};// 存放路由path及callback
this.currentUrl = '';
window.addEventListener('hashchange', this.refresh);
}
// 切換路由後執行
Route.prototype.refresh = function(){
console.log(location.hash +' dom is refresh')
}
// 事件和路由綁定
Route.prototype.route = function(path, callback){
this.routes[path] = callback;
}
// 執行事件
Route.prototype.push = function(path){
//更新視圖 dosomething
location.hash = path;
this.currentUrl = location.hash.slice(1) || '/';
this.routes[this.currentUrl] && this.routes[this.currentUrl]();
}
Route.prototype.replace = function(path){
//更新視圖 dosomething
const i = location.href.indexOf('#');
location.replace(location.href.slice(0, i >= 0 ? i : 0) + '#' + path);
this.routes[path] && this.routes[path]();
}
var myRouter = new Route();
// 定義路由事件
myRouter.route('my', () => {
console.log('page1');
})
myRouter.route('home', () => {
console.log('page2');
})
// 使用路由
myRouter.push('my');
myRouter.replace('home');
複製代碼
history
function Route(){
this.routes = {};// 存放路由path及callback
window.addEventListener('popState', this.refresh)
}
Route.prototype.refresh = function(path){
console.log('dom has refresh');
}
// 事件和路由綁定
Route.prototype.route = function(path, callback){
this.routes[path] = callback;
}
Route.prototype.replace = function (path) {
//更新視圖
history.replaceState({path: path}, null, path);
this.routes[path] && this.routes[path]();
}
Route.prototype.push = function (path) {
//更新視圖
history.pushState({path: path}, null, path);
this.routes[path] && this.routes[path]();
}
var myRouter = new Route();
// 定義路由事件
myRouter.route('/my', () => {
console.log('page1');
})
myRouter.route('/detail', () => {
console.log('page2');
})
// 使用路由
myRouter.push('/detail')
複製代碼
vue-router源碼導讀 1-1 VueRouter構造函數
// src/index.js
export default class VueRouter{
mode: string; // 傳入的字符串參數,指示history類別
history: HashHistory | HTML5History | AbstractHistory; // 實際起做用的對象屬性,必須是以上三個類的枚舉
fallback: boolean; // 如瀏覽器不支持,'history'模式需回滾爲'hash'模式
constructor (options: RouterOptions = {}) {
let mode = options.mode || 'hash' // 默認爲'hash'模式
this.fallback = mode === 'history' && !supportsPushState // 經過supportsPushState判斷瀏覽器是否支持'history'模式
if (this.fallback) {
mode = 'hash'
}
if (!inBrowser) {
mode = 'abstract' // 不在瀏覽器環境下運行需強制爲'abstract'模式
}
this.mode = mode
// 根據mode肯定history實際的類並實例化
switch (mode) {
case 'history':
this.history = new HTML5History(this, options.base)
break
case 'hash':
this.history = new HashHistory(this, options.base, this.fallback)
break
case 'abstract':
this.history = new AbstractHistory(this, options.base)
break
default:
if (process.env.NODE_ENV !== 'production') {
assert(false, `invalid mode: ${mode}`)
}
}
}
init (app: any /* Vue component instance */) {
const history = this.history
// 根據history的類別執行相應的初始化操做和監聽
if (history instanceof HTML5History) {
history.transitionTo(history.getCurrentLocation())
} else if (history instanceof HashHistory) {
const setupHashListener = () => {
history.setupListeners()
}
history.transitionTo(
history.getCurrentLocation(),
setupHashListener,
setupHashListener
)
}
history.listen(route => {
this.apps.forEach((app) => {
app._route = route
})
})
}
// VueRouter類暴露的如下方法實際是調用具體history對象的方法
push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
this.history.push(location, onComplete, onAbort)
}
replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {
this.history.replace(location, onComplete, onAbort)
}
}
複製代碼
1-2 HashHistory 流程:
$router.push()-->HashHistory.push()-->History.transitionTo()-->History.updateRoute()-->{app._route=route}-->vm.render()
HashHistory.push()
push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
this.transitionTo(location, route => {
pushHash(route.fullPath)
onComplete && onComplete(route)
}, onAbort)
}
function pushHash (path) {
window.location.hash = path //對hash進行直接賦值
}
複製代碼
HashHistory.replace()
replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {
this.transitionTo(location, route => {
replaceHash(route.fullPath)
onComplete && onComplete(route)
}, onAbort)
}
function replaceHash (path) {
const i = window.location.href.indexOf('#')
window.location.replace(
window.location.href.slice(0, i >= 0 ? i : 0) + '#' + path //調用location.replace()直接替換hash
)
}
複製代碼
1-3 transitionTo()
//父類History中的transitionTo()方法
transitionTo (location: RawLocation, onComplete?: Function, onAbort?: Function) {
const route = this.router.match(location, this.current)
this.confirmTransition(route, () => {
this.updateRoute(route)
...
})
}
updateRoute (route: Route) {
this.cb && this.cb(route)
}
listen (cb: Function) {
this.cb = cb
}
//VueRouter類中定義了listen方法
init (app: any /* Vue component instance */) {
this.apps.push(app)
history.listen(route => {
this.apps.forEach((app) => {
app._route = route
})
})
}
//install.js在插件加載的地方混入了_route
// 經過Vue.mixin()方法,全局註冊一個混合,影響註冊以後全部建立的每一個Vue實例,
// 該混合在beforeCreate鉤子中經過Vue.util.defineReactive()定義了響應式的_route屬性。
// 所謂響應式屬性,即當_route值改變時,會自動調用Vue實例的render()方法,更新視圖
export function install (Vue) {
Vue.mixin({
beforeCreate () {
if (isDef(this.$options.router)) {
this._router = this.$options.router
this._router.init(this)
Vue.util.defineReactive(this, '_route', this._router.history.current)
}
registerInstance(this, this)
},
})
}
複製代碼
1-4 監聽地址欄
setupListeners () {
window.addEventListener('hashchange', () => {
if (!ensureSlash()) {
return
}
this.transitionTo(getHash(), route => {
replaceHash(route.fullPath)
})
})
}
複製代碼
1-5 HTML5History
push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
const { current: fromRoute } = this
this.transitionTo(location, route => {
pushState(cleanPath(this.base + route.fullPath))
handleScroll(this.router, route, fromRoute, false)
onComplete && onComplete(route)
}, onAbort)
}
replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {
const { current: fromRoute } = this
this.transitionTo(location, route => {
replaceState(cleanPath(this.base + route.fullPath))
handleScroll(this.router, route, fromRoute, false)
onComplete && onComplete(route)
}, onAbort)
}
// src/util/push-state.js
export function pushState (url?: string, replace?: boolean) {
saveScrollPosition()
// try...catch the pushState call to get around Safari
// DOM Exception 18 where it limits to 100 pushState calls
const history = window.history
try {
if (replace) {
history.replaceState({ key: _key }, '', url)
} else {
_key = genKey()
history.pushState({ key: _key }, '', url)
}
} catch (e) {
window.location[replace ? 'replace' : 'assign'](url)
}
}
export function replaceState (url?: string) {
pushState(url, true)
}
複製代碼
popState監聽地址欄變化
constructor (router: Router, base: ?string) {
window.addEventListener('popstate', e => {
const current = this.current
this.transitionTo(getLocation(this.base), route => {
if (expectScroll) {
handleScroll(router, route, current, true)
}
})
})
}
複製代碼
supportsPushState檢查瀏覽器是否支持HTML5的history
// src/util/push-state.js
export const supportsPushState = inBrowser && (function () {
const ua = window.navigator.userAgent
if (
(ua.indexOf('Android 2.') !== -1 || ua.indexOf('Android 4.0') !== -1) &&
ua.indexOf('Mobile Safari') !== -1 &&
ua.indexOf('Chrome') === -1 &&
ua.indexOf('Windows Phone') === -1
) {
return false
}
return window.history && 'pushState' in window.history
})()
複製代碼