先上github項目地址: spa-routers
運行效果圖
javascript
用了許多前端框架來作spa
應用,好比說backbone,angular,vue
他們都有各自的路由系統,管理着前端的每個頁面切換,想要理解其中路由的實現,最好的方法就是手動實現一個。
前端路由有2種實現方式,一種是html5推出的historyapi
,咱們這裏說的是另外一種hash
路由,就是常見的 #
號,這種方式兼容性更好。html
咱們這裏只是簡單的實現一個路由輪子,基本的功能包含如下:前端
切換頁面vue
異步加載jshtml5
異步傳參java
切換頁面:路由的最大做用就是切換頁面,以日後臺的路由是直接改變了頁面的url方式促使頁面刷新。可是前端路由經過 # 號不能刷新頁面,只能經過 window 的監聽事件 hashchange 來監聽hash的變化,而後捕獲到具體的hash值進行操做git
//路由切換 window.addEventListener('hashchange',function(){ //do something this.hashChange() })
註冊路由:咱們須要把路由規則註冊到頁面,這樣頁面在切換的時候纔會有不一樣的效果。github
//註冊函數 map:function(path,callback){ path = path.replace(/\s*/g,"");//過濾空格 //在有回調,且回調是一個正確的函數的狀況下進行存儲 以 /name 爲key的對象 {callback:xx} if(callback && Object.prototype.toString.call(callback) === '[object Function]' ){ this.routers[path] ={ callback:callback,//回調 fn:null //存儲異步文件狀態,用來記錄異步的js文件是否下載,下文有說起 } }else{ //打印出錯的堆棧信息 console.trace('註冊'+path+'地址須要提供正確的的註冊回調') } } //調用方式 map('/detail',function(transition){ ... })
異步加載js:通常單頁面應用爲了性能優化,都會把各個頁面的文件拆分開,按需加載,因此路由裏面要加入異步加載js文件的功能。異步加載咱們就採用最簡單的原生方法,建立script標籤,動態引入js。json
var _body= document.getElementsByTagName('body')[0], scriptEle= document.createElement('script'); scriptEle.type= 'text/javascript'; scriptEle.src= xxx.js; scriptEle.async = true; scriptEle.onload= function(callback){ //爲了不重複引入js,咱們須要在這裏記錄一下已經加載過的文件,對應的 fn須要賦值處理 callback() } _body.appendChild(scriptEle);
參數傳遞:在咱們動態引入單獨模塊的js以後,咱們可能須要給這個模塊傳遞一些單獨的參數。這裏借鑑了一下jsonp的處理方式,咱們把單獨模塊的js包裝成一個函數,提供一個全局的回調方法,加載完成時候再調用回調函數。api
SPA_RESOLVE_INIT = function(transition) { document.getElementById("content").innerHTML = '<p style="color:#F8C545;">當前異步渲染列表頁'+ JSON.stringify(transition) +'</p>' console.log("首頁回調" + JSON.stringify(transition)) }
擴展:以上咱們已經完成了基本功能,咱們再對齊進行擴展,在頁面切換以前beforeEach
和切換完成afterEach
的時候增長2個方法進行處理。思路是,註冊了這2個方法以後,在切換以前就調用beforeEach
,切換以後,須要等待下載js完成,在onload
裏面進行調用 afterEach
//切換以前一些處理 beforeEach:function(callback){ if(Object.prototype.toString.call(callback) === '[object Function]'){ this.beforeFun = callback; }else{ console.trace('路由切換前鉤子函數不正確') } }, //切換成功以後 afterEach:function(callback){ if(Object.prototype.toString.call(callback) === '[object Function]'){ this.afterFun = callback; }else{ console.trace('路由切換後回調函數不正確') } },
經過以上的思路分析,再加以整合,咱們就完成了一個簡單的前端路由,而且能夠加到頁面進行實際的SPA開發,不過仍是很是簡陋。
/* *author:https://github.com/kliuj **使用方法 * 1:註冊路由 : spaRouters.map('/name',function(transition){ //異步加載js spaRouters.asyncFun('name.js',transition) //或者同步執行回調 spaRouters.syncFun(function(transition){},transition) }) 2:初始化 spaRouters.init() 3:跳轉 href = '#/name' */ (function() { var util = { //獲取路由的路徑和詳細參數 getParamsUrl:function(){ var hashDeatail = location.hash.split("?"), hashName = hashDeatail[0].split("#")[1],//路由地址 params = hashDeatail[1] ? hashDeatail[1].split("&") : [],//參數內容 query = {}; for(var i = 0;i<params.length ; i++){ var item = params[i].split("="); query[item[0]] = item[1] } return { path:hashName, query:query } } } function spaRouters(){ this.routers = {};//保存註冊的全部路由 this.beforeFun = null;//切換前 this.afterFun = null; } spaRouters.prototype={ init:function(){ var self = this; //頁面加載匹配路由 window.addEventListener('load',function(){ self.urlChange() }) //路由切換 window.addEventListener('hashchange',function(){ self.urlChange() }) //異步引入js經過回調傳遞參數 window.SPA_RESOLVE_INIT = null; }, refresh:function(currentHash){ var self = this; if(self.beforeFun){ self.beforeFun({ to:{ path:currentHash.path, query:currentHash.query }, next:function(){ self.routers[currentHash.path].callback.call(self,currentHash) } }) }else{ self.routers[currentHash.path].callback.call(self,currentHash) } }, //路由處理 urlChange:function(){ var currentHash = util.getParamsUrl(); if(this.routers[currentHash.path]){ this.refresh(currentHash) }else{ //不存在的地址重定向到首頁 location.hash = '/index' } }, //單層路由註冊 map:function(path,callback){ path = path.replace(/\s*/g,"");//過濾空格 if(callback && Object.prototype.toString.call(callback) === '[object Function]' ){ this.routers[path] ={ callback:callback,//回調 fn:null //存儲異步文件狀態 } }else{ console.trace('註冊'+path+'地址須要提供正確的的註冊回調') } }, //切換以前一些處理 beforeEach:function(callback){ if(Object.prototype.toString.call(callback) === '[object Function]'){ this.beforeFun = callback; }else{ console.trace('路由切換前鉤子函數不正確') } }, //切換成功以後 afterEach:function(callback){ if(Object.prototype.toString.call(callback) === '[object Function]'){ this.afterFun = callback; }else{ console.trace('路由切換後回調函數不正確') } }, //路由異步懶加載js文件 asyncFun:function(file,transition){ var self = this; if(self.routers[transition.path].fn){ self.afterFun && self.afterFun(transition) self.routers[transition.path].fn(transition) }else{ console.log("開始異步下載js文件"+file) var _body= document.getElementsByTagName('body')[0]; var scriptEle= document.createElement('script'); scriptEle.type= 'text/javascript'; scriptEle.src= file; scriptEle.async = true; SPA_RESOLVE_INIT = null; scriptEle.onload= function(){ console.log('下載'+file+'完成') self.afterFun && self.afterFun(transition) self.routers[transition.path].fn = SPA_RESOLVE_INIT; self.routers[transition.path].fn(transition) } _body.appendChild(scriptEle); } }, //同步操做 syncFun:function(callback,transition){ this.afterFun && this.afterFun(transition) callback && callback(transition) } } //註冊到window全局 window.spaRouters = new spaRouters(); })()
簡單的單頁面在github上有完整的demo
spa-routers
以上僅是我我的的一些見解,若有疑問,感謝指導