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
})()
复制代码