NEJ-Dispatcher剖析(一) History

勿忘初心2018-10-26 11:26

此文已由作者杨介正授权网易云社区发布。

欢迎访问网易云社区,了解更多网易技术产品运营经验。


1背景

1.1 什么是history

  history 指浏览器的浏览历史,用于存放浏览器的浏览记录。每条记录又被称作history entry,history entry 是一个对象,包括url、dom树、状态对象等等。
1.2 什么时候产生history entry
  1 浏览器URL发生变化:浏览器自动保存变化前的历史记录。如url由http://a.com变成http://b.com或者http://a.com/#a变成http://a.com/#b。这里需要注意的是,IE7及以下浏览器,对URL的hash值变化(这里是#a变成#b),是不会自动保存历史记录的,我们需要通过其他方式进行手动模拟,具体方法会在后文说明。
  2 pushstate( stateObject, title, url)方法:对支持该方法的浏览器,此方法允许开发者手动向history添加history entry。
  3 document.open:当重新打开一文档流的时候,浏览器会保存一个历史记录。
  4 iframe窗口的history entry也会被保存到主窗口的history中。
  好了,继续上面遗留的问题,对于IE7及以下浏览器,hash值变化浏览器是不会自动保存历史记录的,需要我们手动为其保存一个历史记录。方法也很简单,就是在主窗口中放置一个iframe并轮询主窗口的hash值。如果hash值发生变化,则让iframe重新打开一个文档流,这样iframe会产生一个history entry并添加到主窗口的history中。当然,你可以往新打开的流中写入你需要的信息以便后面使用。
1.3 history的用处
减少服务器的请求:通过浏览器的前进和后退按钮进行历史记录切换,除非页面申明不缓存,浏览器是不会发送页面请求给服务器的,而是直接从自己的缓存中提取页面信息。

2 NEJ模块调度系统(dispatcher)中的History
2.1 dispatcher的基本原理
dispatcher是 通过解析URL的hash值或者path(支持pushstate方法)来实现模块调度的。比如#shop,是显示整个商场场地;#shop/snack,就是显示商场场地的同时显示零食区域。
2.2 Dispatcher中的history
  History模块负责对url值进行监听并通过轮询加iframe的方式(见上文)手动对IE7及以下浏览器保存history entry,为IE7及以下浏览器保存历史记录主要是方便浏览器的前进后退按钮的操作。当url发生变化时,将hash或者pathname传给dispatcher进行解析。监听url的方式分为两种,分别是注册hashchange事件方式和轮询url方式。使用hashchange事件方式需要浏览器支持hashchange事件,这时history将会把hash值传给dispatcher进行解析。使用轮询url方式又分两种情况,第一种是IE7及以下浏览器,因为不支持hashchange事件,不得不用这种方式;第二种是对于支持pushState方法的浏览器,开发者希望用pushState和replaceState的方式代替hash的方式来实现不刷新页面的历史记录跳转, 这时history将会把pathname传给dispatcher进行解析

3总结
  浏览器的history对象可以简单的理解成一个存放history entry的数组,每个history entry 是一个包含url、dom、stateObject等信息的对象。改变url、打开新的文档流会产生一个history entry,hash值得改变不会引起IE8-浏览器保存历史记录,需要通过在iframe中手动打开新文档流进行模拟添加。NEJ-dispatcher是通过解析hash或者pathname进行模块的显示和掩藏,history模块负责对url的监听并通知dispatcher进行解析。

示例代码
h = {
	//cross-platform add event to dom
	_addEvent: function(dom, type, callback){

		if('addEventListener' in window){
			dom.addEventListener(type, callback, false);
		}else{
			dom.attachEvent('on' + type, callback);
		}
	},

	_bind:function(fun, context){
		return function(){
			fun.apply(context, arguments);
		}
	},

	_useHash: function(){
		var
			ua = /(msie)\s(\w+)/.exec(navigator.userAgent.toLowerCase());
		return !ua || ua[2] > 7 && 'onhashchange' in window;
	},

	//get hash from url
	_getHash: function(){
		return /#\/?(.*)\/?/.exec(location.href)[1];
	},

	_doCallbacks: function(params){
		console.log('here is callback')
	},

	_synHash: function(){
		var
			current = this._getHash(),
			iHash = this.iWin ? this.iWin.location.hash.replace(/#/, '') : '',
			old = this.iWin ? iHash : this.oldValue,
			params;

		old == undefined && (old = '');
		if(current == old) return;

		//prevent default behavior if the beforeChange method reutrn false
		params = {
			oldValue: old,
			newValue: current
		}

		if(this.beforeChange(params) === false){
			this.navigate(old);
			return;
		} 

		if(this.iWin){
			
			//is navigation, create history entry
			if(current != this.oldValue){
				this.iWin.document.open();
				this.iWin.document.close();				
				this.iWin.location.hash = current;
			}
			//just move history
			else{					
				location.hash = iHash;
				current = iHash;
				params.newValue = iHash;
			}
		}

		this._doCallbacks(params);

		this.oldValue = current;
	},

	beforeChange: function(params){
		return true
	},

	navigate: function(hash){
		location.hash = hash;
	},

	bootstrap: function(){
		var
			iframe,
			iWin;
		if(!location.hash){
			location.hash = '';
		}
		this.oldValue = this._getHash();
		if(this._useHash()){
			this._addEvent(window, 'hashchange', this._bind(this._synHash, this));
		}else{
			iframe = document.createElement('iframe');
			iframe.src = 'javascript:void(0)';
			iframe.style.display = 'none';
			iframe.tabIndex = -1;
			iWin  = this.iWin = document.body.insertBefore(iframe, document.body.firstChild).contentWindow;
			iWin.document.open();
			//iWin.document.close();				
			iWin.location.hash = this._getHash();
			this.timer = setInterval(this._bind(this._synHash, this), 50);
		}
	}
}

h.bootstrap()


网易云免费体验馆,0成本体验20+款云产品! 

更多网易技术、产品、运营经验分享请点击