History API
API
history.back(); history.go(-1)
history.forward();history.go(1)
history.pushState(state,unused,url?)
,state:可序列化的js object.unused:历史原因,空字符串,url:可选,当页面恢复后有可能会加载它,但是必须同源。它会用来更新浏览器的地址栏,但是它不会加载那个页面。history.replaceState(state,unused,url?)
事件
- popstate
当调用浏览器的前进/后退,或者history.forward|back|go
,导致历史栈变更,并且state发生变化的时候会发布这个事件 - hashChange
当url只有fragment部分发生变化,会触发这个事件,在popstate 事件之后。
SPA
上面的APIpushState,replaceState,
,事件popstate
,这两者就是为了SPA的路由系统准备的。所有SPA的路由都是基于它们。
- 用户点击link导致路由跳转,实际上并不是浏览器的路由跳转,内部就两个逻辑
pushState({...state,navigationId},'',newurl);internalRouter();
, - 同时SPA订阅了
window.addEventListeners('popstate',(event)=>{render(event.state)})
下面是Angular源码中对于这一段的解析。
// Angular中通过location/history的操作是封装到了BrowserPlatformLocation 这个类中
export class BrowserPlatformLocation extends PlatformLocation{
// 这个就是window.location
public readonly location!:Location;
// 这个就是window.history
private _history!: History;
constructor(@Inject(DOCUMENT) private _doc: any) {
super();
this._init();
}
_init() {
(this as {location: Location}).location = window.location;
this._history = window.history;
}
// 对浏览器的popState事件进行订阅,重点关注这个方法。这个方法是在LocationStragetry中被调用
override onPopState(fn: LocationChangeListener): VoidFunction {
const window = getDOM().getGlobalEventTarget(this._doc, 'window');
window.addEventListener('popstate', fn, false);
return () => window.removeEventListener('popstate', fn);
}
// 对浏览器的hashChange事件进行订阅
override onHashChange(fn: LocationChangeListener): VoidFunction {
const window = getDOM().getGlobalEventTarget(this._doc, 'window');
window.addEventListener('hashchange', fn, false);
return () => window.removeEventListener('hashchange', fn);
}
override pushState(state: any, title: string, url: string): void {
if (supportsState()) {
this._history.pushState(state, title, url);
} else {
this.location.hash = url;
}
}
override replaceState(state: any, title: string, url: string): void {
if (supportsState()) {
this._history.replaceState(state, title, url);
} else {
this.location.hash = url;
}
}
override forward(): void {
this._history.forward();
}
override back(): void {
this._history.back();
}
}
// LocationStrategy 这个是Angular 对于路由形式的抽象,支持hash/h5路由。这个类使用了装饰者模式来管理底层事件的订阅与解订阅
export class PathLocationStrategy extends LocationStrategy implements OnDestroy {
constructor(
private _platformLocation: PlatformLocation,
@Optional() @Inject(APP_BASE_HREF) href?: string)
{}
// 这个函数对BrowserPlatformLocation 中的popstate,hashchange进行了调用,这里也只是封装,最终的事件处理函数也不是这个类创建的
override onPopState(fn: LocationChangeListener): void {
this._removeListenerFns.push(
this._platformLocation.onPopState(fn), this._platformLocation.onHashChange(fn));
}
}
// Location 这个类再次封装了LocationStrategy,提供了对history的操作,以及对于popstate 的订阅。它的出现是为了支持不同策略的支持
export class Location implements OnDestroy
{
_subject: EventEmitter<any> = new EventEmitter();
// 注入LocationStrategy
constructor(locationStrategy: LocationStrategy) {
this._locationStrategy = locationStrategy;
const browserBaseHref = this._locationStrategy.getBaseHref();
this._baseHref = stripTrailingSlash(_stripIndexHtml(browserBaseHref));
// 这个地方就是对popState事件订阅的地方,它将事件转化成了流
this._locationStrategy.onPopState((ev) => {
this._subject.emit({
'url': this.path(true),
'pop': true,
'state': ev.state,
'type': ev.type,
});
});
}
// 这个类就是提供给Router来进行订阅popState的事件
subscribe(
onNext: (value: PopStateEvent) => void, onThrow?: ((exception: any) => void)|null,
onReturn?: (() => void)|null): SubscriptionLike {
return this._subject.subscribe({next: onNext, error: onThrow, complete: onReturn});
}
}
// 对于popstate事件感兴趣的是Router这个类,它可以主动的跳转路由,然后调用pushState,保存状态。
//它也可以被动的,当浏览器路状态变化时,动态的解析路由,然后调用路由跳转的逻辑。
export class Router{
setUpLocationChangeListener(): void {
// 对下面注释,我是有疑惑的,这个事件里面不是有setTimeout吗,它不是会自动触发cd吗,为什么还要下面的注释
// 其实并不是有setTimeout就一定会触发变更,不能这么说,必须要在ngZone里面执行setTimeout,才有可能触发变更
// Zone.current 如果没有代码执行,其实,它应该是root.当有Task执行的时候,它会临时被设置成Task创建的时候的
// Zone. 所以必须要在window.addEventListeners 的时候,在ngZone里面,否则后面的所以操作都是没有意义的。
// Don't need to use Zone.wrap any more, because zone.js
// already patch onPopState, so location change callback will
// run into ngZone
if (!this.locationSubscription) {
this.locationSubscription = this.location.subscribe(event => {
const source = event['type'] === 'popstate' ? 'popstate' : 'hashchange';
if (source === 'popstate') {
// The `setTimeout` was added in #12160 and is likely to support Angular/AngularJS
// hybrid apps.
setTimeout(() => {
const extras: NavigationExtras = {replaceUrl: true};
// Navigations coming from Angular router have a navigationId state
// property. When this exists, restore the state.
const state = event.state?.navigationId ? event.state : null;
if (state) {
const stateCopy = {...state} as Partial<RestoredState>;
delete stateCopy.navigationId;
delete stateCopy.ɵrouterPageId;
if (Object.keys(stateCopy).length !== 0) {
extras.state = stateCopy;
}
}
const urlTree = this.parseUrl(event['url']!);
//这个就是开始调用路由跳转的逻辑
this.scheduleNavigation(urlTree, source, state, extras);
}, 0);
}
});
}
}
}
标签:url,window,popstate,state,API,._,History,history
From: https://www.cnblogs.com/kongshu-612/p/18373713