【单页应用】全局控制器app应该干些什么?
生活随笔
收集整理的這篇文章主要介紹了
【单页应用】全局控制器app应该干些什么?
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
前言
之前,我們形成了頁面片相關的mvc結構,但是該結構還僅適用于view(頁面)級,那么真正的全局控制器app應該干些什么事情呢?我覺得至少需要干這些:
功能點
① 提供URL解析機制,以便讓控制器可以根據URL獲得當前是要加載哪個view的實例,比如
http://www.baidu.com/index.html#index
http://www.baidu.com/index
若是使用hashChange實現瀏覽器跳轉便直接取出index這個鍵值;
若是使用pushState方案的話,便需要業務同事給出取出URL鍵值的方法,最終我們需要得到index這個鍵值
② app應該保留各個view的實例,并且維護一個隊列關系
以現在博客園為例,我們可能具有兩個view頁面片:index->detail
我們首次便是加載index這個view,點擊其中一個項目便加載detail這個view,這個時候app是應該同時保存兩個view,并且內部要維系一個訪問順序隊列
這個隊列最好可與瀏覽器保存一致,若不能保存一致,后期便可能會出現點擊瀏覽器后退死循環的問題
③ app應該提供view實例化的方法
所以的view實例若無特殊原因,皆應該由app生成,app應該具有實例化view的能力,view一般使用AMD規范管理,這里涉及異步加載
PS:真實工作環境中,view需要自建一套事件機制,比如實例化時候要觸發什么事件,顯示時候要觸發什么事件,皆需要有,app只會負責
實例化->顯示->隱藏
④ app應該提供監控瀏覽器事件,每次自動加載各個view
如上面所述,app會注冊一個hashChange事件或者popState事件以達到改變URL不刷新頁面的功能,這個功能主要用于用戶點擊瀏覽器原生后退鍵
以上便是全局控制器app該干的事情,按程序邏輯說,應該是這樣的
程序邏輯
用戶鍵入一個URL,進到一個單頁應用,于是首次會發生以下事情:
① 屬性初始化,并且為瀏覽器綁定hashChange/popState事件
② 解析URL取出,當前需要加載的VIEW鍵值,一般而言是index,或者會有一些參數
③ 根據鍵值使用requireJS語法加載view類,并且產生實例化操作
④ 實例化結束后,便調用view的show方法,首屏view顯示結束,內部會觸發view自身事件達到頁面渲染的效果
用戶點擊其中一個項目會觸發一個類似forward/back的操作,這個時候流程會有所不同:
① app首先會屏蔽監控瀏覽器的變化,因為這個是用戶主動觸發,不應該觸發hashChange類似事件
② app開始加載forward的view,這里比如是list,將list實例化,然后執行index的hide方法,執行list的show方法,這里便完成了一次view的切換
整個邏輯還可能發生動畫,我們這里暫時忽略。
這時當用戶點擊瀏覽器后退,情況又會有所不同
① app中的hashChange或者popstate會捕捉到這次URL變化
② app會解析這個URL并且安裝之前約定取出鍵值,這個時候會發現app中已經保存了這個view的實例
③ 直接執行list view的hide方法,然后執行index view的show方法,整體邏輯結束
整個app要干的事情基本就是這樣,這種app邏輯一般為3-7百行,代碼少,但是其實現的功能比較復雜,往往是一個單頁應用的核心!
Backbone的控制器
事實上Backbone只有一個History,并不具有控制器的行為,總的來說,Backbone最為有用的就是其view一塊的邏輯,我們很多時候也只是需要這段邏輯
其路由功能本身沒有什么問題,實現也很好,但是我們可以看到他并未完成我們以上需要的功能,所以對我來說,他便只是一個簡單的路由功能,不是控制器
Backbone的路由首先會要求你將一個應用中的所有url與鍵值全部做一個映射,比如
1 var App = Backbone.Router.extend({ 2 ? routes: { 3 ? ? "": "index", ? ?// #index 4 ? ? "index": "index", ? ?// #index 5 ? ? "detail": "detail" ? ?// #detail 6 ? }, 7 ? index: function () { 8 ? ? var index = new Index(this.interface); 9? 10 ? }, 11 ? detail: function () { 12 ? ? var detail = new Detail(this.interface); 13? 14 ? }, 15 ? initialize: function () { 16? 17 ? }, 18 ? interface: { 19 ? ? forward: function (url) { 20 ? ? ? window.location.href = ('#' + url).replace(/^#+/, '#'); 21 ? ? } 22? 23 ? } 24? 25 }); 然后整體的功能完全依賴于URL的變化觸發,那么意味著一個單頁應用中所有的url我都需要在此做映射,我這里當然不愿意這樣做
事實上我做變化時候,只需要一個view類的鍵值即可,所以我們這里便直接跳過了路由映射這個邏輯
每次瀏覽器主動發生的變化,我們直接解析其URL,拿出我們要的view 鍵值,從而加載這個view的實例
我們的控制器
根據前面的想法,我們的控制器一定會包含以下接口:
① 解析URL形成view鍵值的接口,并且該接口可被各業務覆蓋:
getViewIdRules ② 異步加載View類以及實例化view的接口
loadView ③ 瀏覽器事件監聽
buildEvent(hashChange/popState) 以上是幾個關鍵接口,其它接口,如view切換也需要提出,這里我們首先得得出整個app的時序
其時序簡單分為三類,其實還有更加復雜的情況,我們這里暫時不予考慮
① 首先是初始化的操作,首次便只需要解析URL,加載默認view實例并且顯示即可,這個時候雖然注冊了hashChange/popState事件,不會觸發其中邏輯
② 其次是框架主動行為,主動要加載第二個view(view),這個時候便會實例化之,然后觸發自身switchview事件,切換兩個view
③ 最后是瀏覽器觸發hashChange/popState事件,導致框架發生切換view的事件,這個時候兩個view實例已經存在,所以只需要切換即可
PS:每次框架只需要執行簡單的show、hide方法即可,view內部自有其邏輯處理余下事情,這些我們留待后面說
時序圖,出來后,我們就要考慮我們這個全局控制器app,的方法了,這里先給出類圖再做一一實現:
這里做初步的實現:
1 "use strict"; 2 var Application = _.inherit({ 3? 4 ? //設置默認的屬性 5 ? defaultPropery: function () { 6? 7 ? ? //存儲view隊列的hash對象,這里會新建一個hash數據結構,暫時不予理睬 8 ? ? this.views = new _.Hash(); 9? 10 ? ? //當前view 11 ? ? this.curView; 12? 13 ? ? //最后訪問的view 14 ? ? this.lastView; 15? 16 ? ? //各個view的映射地址 17 ? ? this.viewMapping = {}; 18? 19 ? ? //本地維護History邏輯 20 ? ? this.history = []; 21? 22 ? ? //是否開啟路由監控? 23 ? ? this.isListeningRoute = false; 24? 25 ? ? //view的根目錄 26 ? ? this.viewRootPath = 'app/views/'; 27? 28 ? ? //當前對應url請求 29 ? ? this.request = {}; 30? 31 ? ? //當前對應的參數 32 ? ? this.query = {}; 33? 34 ? ? //pushState的支持能力 35 ? ? this.hasPushState = !!(this.history && this.history.pushState); 36? 37 ? ? //由用戶定義的獲取viewid規則 38 ? ? this.getViewIdRules = function (url, hasPushState) { 39 ? ? ? return _.getUrlParam(url, 'viewId'); 40 ? ? }; 41? 42 ? }, 43? 44 ? //@override 45 ? handleOptions: function (opts) { 46 ? ? _.extend(this, opts); 47 ? }, 48? 49 ? initialize: function (opts) { 50? 51 ? ? this.defaultPropery(); 52 ? ? this.handleOptions(opts); 53? 54 ? ? //構造系統各個事件 55 ? ? this.buildEvent(); 56? 57 ? ? //首次動態調用,生成view 58 ? ? this.start(); 59 ? }, 60? 61 ? buildEvent: function () { 62 ? ? this._requireEvent(); 63 ? ? this._routeEvent(); 64 ? }, 65? 66 ? _requireEvent: function () { 67 ? ? requirejs.onError = function (e) { 68 ? ? ? if (e && e.requireModules) { 69 ? ? ? ? for (var i = 0; i < e.requireModules.length; i++) { 70 ? ? ? ? ? console.log('抱歉,當前的網絡狀況不給力,請刷新重試!'); 71 ? ? ? ? ? break; 72 ? ? ? ? } 73 ? ? ? } 74 ? ? }; 75 ? }, 76? 77 ? //路由相關處理邏輯,可能是hash,可能是pushState 78 ? _routeEvent: function () { 79? 80 ? ? //默認使用pushState邏輯,否則使用hashChange,后續出pushState的方案 81 ? ? $(window).bind('hashchange', _.bind(this.onURLChange, this)); 82? 83 ? }, 84? 85 ? //當URL變化時 86 ? onURLChange: function () { 87 ? ? if (!this.isListeningRoute) return; 88? 89 ? }, 90? 91 ? startListeningRoute: function () { 92 ? ? this.isListeningRoute = true; 93 ? }, 94? 95 ? stopListeningRoute: function () { 96 ? ? this.isListeningRoute = false; 97 ? }, 98? 99 ? //解析的當前url,并且根據getViewIdRules生成當前viewID 100 ? parseUrl: function (url) { 101? 102 ? }, 103? 104 ? //入口點 105 ? start: function () { 106 ? ? var url = decodeURIComponent(window.location.hash.replace(/^#+/i, '')).toLowerCase(); 107 ? ? this.history.push(window.location.href); 108 ? ? //處理當前url,會將viewid寫入request對象 109 ? ? this.parseUrl(url); 110? 111 ? ? var viewId = this.request.viewId; 112? 113 ? ? //首次不會觸發路由監聽,直接程序導入 114 ? ? this.switchView(viewId); 115? 116 ? }, 117? 118 ? //根據viewId判斷當前view是否實例化 119 ? viewExist: function (viewId) { 120 ? ? return this.views.exist(viewId); 121 ? }, 122? 123 ? //根據viewid,加載view的類,并會實例化 124 ? //注意,這里只會返回一個view的實例,并不會顯示或者怎樣,也不會執行app的邏輯 125 ? loadView: function (viewId, callback) { 126? 127 ? ? //每個鍵值還是在全局views保留一個存根,若是已經加載過便不予理睬 128 ? ? if (this.viewExist(viewId)) { 129 ? ? ? _.callmethod(callback, this, this.views.get(viewId)); 130 ? ? ? return; 131 ? ? } 132? 133 ? ? requirejs([this._buildPath(viewId)], $.proxy(function (View) { 134 ? ? ? var view = new View(); 135? 136 ? ? ? this.views.push(viewId, view); 137? 138 ? ? ? //將當前view實例傳入,執行回調 139 ? ? ? _.callmethod(callback, this, view); 140? 141 ? ? }, this)); 142 ? }, 143? 144 ? //根據viewId生成路徑 145 ? _buildPath: function (viewId) { 146 ? ? return this.viewMapping[viewId] ? this.viewMapping[viewId] : this.viewRootPath + viewId; 147 ? }, 148? 149 ? //注意,此處的url可能是id,也可能是其它莫名其妙的,這里需要進行解析 150 ? forward: function (viewId) { 151? 152 ? ? //解析viewId邏輯暫時省略 153 ? ? //...... 154 ? ? this.switchView(viewId); 155? 156 ? }, 157? 158 ? //后退操作 159 ? back: function () { 160? 161 ? }, 162? 163 ? //view切換,傳入要顯示和隱藏的view實例 164 ? switchView: function (viewId) { 165 ? ? if (!viewId) return; 166? 167 ? ? this.loadView(viewId, function (view) { 168 ? ? ? this.lastView = this.curView; 169 ? ? ? this.curView = view; 170? 171 ? ? ? if (this.curView) this.curView.show(); 172 ? ? ? if (this.lastView) this.lastView.show(); 173? 174 ? ? }); 175 ? } 176? 177 }); 結語
今天,我們一起分析了全局控制器app應該做些什么,并且整理了下基本思路,那么我們這個星期的主要目的便是實現這個app,今日到此結束。
本文轉自葉小釵博客園博客,原文鏈接:http://www.cnblogs.com/yexiaochai/p/3764376.html,如需轉載請自行聯系原作者
之前,我們形成了頁面片相關的mvc結構,但是該結構還僅適用于view(頁面)級,那么真正的全局控制器app應該干些什么事情呢?我覺得至少需要干這些:
功能點
① 提供URL解析機制,以便讓控制器可以根據URL獲得當前是要加載哪個view的實例,比如
http://www.baidu.com/index.html#index
http://www.baidu.com/index
若是使用hashChange實現瀏覽器跳轉便直接取出index這個鍵值;
若是使用pushState方案的話,便需要業務同事給出取出URL鍵值的方法,最終我們需要得到index這個鍵值
② app應該保留各個view的實例,并且維護一個隊列關系
以現在博客園為例,我們可能具有兩個view頁面片:index->detail
我們首次便是加載index這個view,點擊其中一個項目便加載detail這個view,這個時候app是應該同時保存兩個view,并且內部要維系一個訪問順序隊列
這個隊列最好可與瀏覽器保存一致,若不能保存一致,后期便可能會出現點擊瀏覽器后退死循環的問題
③ app應該提供view實例化的方法
所以的view實例若無特殊原因,皆應該由app生成,app應該具有實例化view的能力,view一般使用AMD規范管理,這里涉及異步加載
PS:真實工作環境中,view需要自建一套事件機制,比如實例化時候要觸發什么事件,顯示時候要觸發什么事件,皆需要有,app只會負責
實例化->顯示->隱藏
④ app應該提供監控瀏覽器事件,每次自動加載各個view
如上面所述,app會注冊一個hashChange事件或者popState事件以達到改變URL不刷新頁面的功能,這個功能主要用于用戶點擊瀏覽器原生后退鍵
以上便是全局控制器app該干的事情,按程序邏輯說,應該是這樣的
程序邏輯
用戶鍵入一個URL,進到一個單頁應用,于是首次會發生以下事情:
① 屬性初始化,并且為瀏覽器綁定hashChange/popState事件
② 解析URL取出,當前需要加載的VIEW鍵值,一般而言是index,或者會有一些參數
③ 根據鍵值使用requireJS語法加載view類,并且產生實例化操作
④ 實例化結束后,便調用view的show方法,首屏view顯示結束,內部會觸發view自身事件達到頁面渲染的效果
用戶點擊其中一個項目會觸發一個類似forward/back的操作,這個時候流程會有所不同:
① app首先會屏蔽監控瀏覽器的變化,因為這個是用戶主動觸發,不應該觸發hashChange類似事件
② app開始加載forward的view,這里比如是list,將list實例化,然后執行index的hide方法,執行list的show方法,這里便完成了一次view的切換
整個邏輯還可能發生動畫,我們這里暫時忽略。
這時當用戶點擊瀏覽器后退,情況又會有所不同
① app中的hashChange或者popstate會捕捉到這次URL變化
② app會解析這個URL并且安裝之前約定取出鍵值,這個時候會發現app中已經保存了這個view的實例
③ 直接執行list view的hide方法,然后執行index view的show方法,整體邏輯結束
整個app要干的事情基本就是這樣,這種app邏輯一般為3-7百行,代碼少,但是其實現的功能比較復雜,往往是一個單頁應用的核心!
Backbone的控制器
事實上Backbone只有一個History,并不具有控制器的行為,總的來說,Backbone最為有用的就是其view一塊的邏輯,我們很多時候也只是需要這段邏輯
其路由功能本身沒有什么問題,實現也很好,但是我們可以看到他并未完成我們以上需要的功能,所以對我來說,他便只是一個簡單的路由功能,不是控制器
Backbone的路由首先會要求你將一個應用中的所有url與鍵值全部做一個映射,比如
1 var App = Backbone.Router.extend({ 2 ? routes: { 3 ? ? "": "index", ? ?// #index 4 ? ? "index": "index", ? ?// #index 5 ? ? "detail": "detail" ? ?// #detail 6 ? }, 7 ? index: function () { 8 ? ? var index = new Index(this.interface); 9? 10 ? }, 11 ? detail: function () { 12 ? ? var detail = new Detail(this.interface); 13? 14 ? }, 15 ? initialize: function () { 16? 17 ? }, 18 ? interface: { 19 ? ? forward: function (url) { 20 ? ? ? window.location.href = ('#' + url).replace(/^#+/, '#'); 21 ? ? } 22? 23 ? } 24? 25 }); 然后整體的功能完全依賴于URL的變化觸發,那么意味著一個單頁應用中所有的url我都需要在此做映射,我這里當然不愿意這樣做
事實上我做變化時候,只需要一個view類的鍵值即可,所以我們這里便直接跳過了路由映射這個邏輯
每次瀏覽器主動發生的變化,我們直接解析其URL,拿出我們要的view 鍵值,從而加載這個view的實例
我們的控制器
根據前面的想法,我們的控制器一定會包含以下接口:
① 解析URL形成view鍵值的接口,并且該接口可被各業務覆蓋:
getViewIdRules ② 異步加載View類以及實例化view的接口
loadView ③ 瀏覽器事件監聽
buildEvent(hashChange/popState) 以上是幾個關鍵接口,其它接口,如view切換也需要提出,這里我們首先得得出整個app的時序
其時序簡單分為三類,其實還有更加復雜的情況,我們這里暫時不予考慮
① 首先是初始化的操作,首次便只需要解析URL,加載默認view實例并且顯示即可,這個時候雖然注冊了hashChange/popState事件,不會觸發其中邏輯
② 其次是框架主動行為,主動要加載第二個view(view),這個時候便會實例化之,然后觸發自身switchview事件,切換兩個view
③ 最后是瀏覽器觸發hashChange/popState事件,導致框架發生切換view的事件,這個時候兩個view實例已經存在,所以只需要切換即可
PS:每次框架只需要執行簡單的show、hide方法即可,view內部自有其邏輯處理余下事情,這些我們留待后面說
時序圖,出來后,我們就要考慮我們這個全局控制器app,的方法了,這里先給出類圖再做一一實現:
這里做初步的實現:
1 "use strict"; 2 var Application = _.inherit({ 3? 4 ? //設置默認的屬性 5 ? defaultPropery: function () { 6? 7 ? ? //存儲view隊列的hash對象,這里會新建一個hash數據結構,暫時不予理睬 8 ? ? this.views = new _.Hash(); 9? 10 ? ? //當前view 11 ? ? this.curView; 12? 13 ? ? //最后訪問的view 14 ? ? this.lastView; 15? 16 ? ? //各個view的映射地址 17 ? ? this.viewMapping = {}; 18? 19 ? ? //本地維護History邏輯 20 ? ? this.history = []; 21? 22 ? ? //是否開啟路由監控? 23 ? ? this.isListeningRoute = false; 24? 25 ? ? //view的根目錄 26 ? ? this.viewRootPath = 'app/views/'; 27? 28 ? ? //當前對應url請求 29 ? ? this.request = {}; 30? 31 ? ? //當前對應的參數 32 ? ? this.query = {}; 33? 34 ? ? //pushState的支持能力 35 ? ? this.hasPushState = !!(this.history && this.history.pushState); 36? 37 ? ? //由用戶定義的獲取viewid規則 38 ? ? this.getViewIdRules = function (url, hasPushState) { 39 ? ? ? return _.getUrlParam(url, 'viewId'); 40 ? ? }; 41? 42 ? }, 43? 44 ? //@override 45 ? handleOptions: function (opts) { 46 ? ? _.extend(this, opts); 47 ? }, 48? 49 ? initialize: function (opts) { 50? 51 ? ? this.defaultPropery(); 52 ? ? this.handleOptions(opts); 53? 54 ? ? //構造系統各個事件 55 ? ? this.buildEvent(); 56? 57 ? ? //首次動態調用,生成view 58 ? ? this.start(); 59 ? }, 60? 61 ? buildEvent: function () { 62 ? ? this._requireEvent(); 63 ? ? this._routeEvent(); 64 ? }, 65? 66 ? _requireEvent: function () { 67 ? ? requirejs.onError = function (e) { 68 ? ? ? if (e && e.requireModules) { 69 ? ? ? ? for (var i = 0; i < e.requireModules.length; i++) { 70 ? ? ? ? ? console.log('抱歉,當前的網絡狀況不給力,請刷新重試!'); 71 ? ? ? ? ? break; 72 ? ? ? ? } 73 ? ? ? } 74 ? ? }; 75 ? }, 76? 77 ? //路由相關處理邏輯,可能是hash,可能是pushState 78 ? _routeEvent: function () { 79? 80 ? ? //默認使用pushState邏輯,否則使用hashChange,后續出pushState的方案 81 ? ? $(window).bind('hashchange', _.bind(this.onURLChange, this)); 82? 83 ? }, 84? 85 ? //當URL變化時 86 ? onURLChange: function () { 87 ? ? if (!this.isListeningRoute) return; 88? 89 ? }, 90? 91 ? startListeningRoute: function () { 92 ? ? this.isListeningRoute = true; 93 ? }, 94? 95 ? stopListeningRoute: function () { 96 ? ? this.isListeningRoute = false; 97 ? }, 98? 99 ? //解析的當前url,并且根據getViewIdRules生成當前viewID 100 ? parseUrl: function (url) { 101? 102 ? }, 103? 104 ? //入口點 105 ? start: function () { 106 ? ? var url = decodeURIComponent(window.location.hash.replace(/^#+/i, '')).toLowerCase(); 107 ? ? this.history.push(window.location.href); 108 ? ? //處理當前url,會將viewid寫入request對象 109 ? ? this.parseUrl(url); 110? 111 ? ? var viewId = this.request.viewId; 112? 113 ? ? //首次不會觸發路由監聽,直接程序導入 114 ? ? this.switchView(viewId); 115? 116 ? }, 117? 118 ? //根據viewId判斷當前view是否實例化 119 ? viewExist: function (viewId) { 120 ? ? return this.views.exist(viewId); 121 ? }, 122? 123 ? //根據viewid,加載view的類,并會實例化 124 ? //注意,這里只會返回一個view的實例,并不會顯示或者怎樣,也不會執行app的邏輯 125 ? loadView: function (viewId, callback) { 126? 127 ? ? //每個鍵值還是在全局views保留一個存根,若是已經加載過便不予理睬 128 ? ? if (this.viewExist(viewId)) { 129 ? ? ? _.callmethod(callback, this, this.views.get(viewId)); 130 ? ? ? return; 131 ? ? } 132? 133 ? ? requirejs([this._buildPath(viewId)], $.proxy(function (View) { 134 ? ? ? var view = new View(); 135? 136 ? ? ? this.views.push(viewId, view); 137? 138 ? ? ? //將當前view實例傳入,執行回調 139 ? ? ? _.callmethod(callback, this, view); 140? 141 ? ? }, this)); 142 ? }, 143? 144 ? //根據viewId生成路徑 145 ? _buildPath: function (viewId) { 146 ? ? return this.viewMapping[viewId] ? this.viewMapping[viewId] : this.viewRootPath + viewId; 147 ? }, 148? 149 ? //注意,此處的url可能是id,也可能是其它莫名其妙的,這里需要進行解析 150 ? forward: function (viewId) { 151? 152 ? ? //解析viewId邏輯暫時省略 153 ? ? //...... 154 ? ? this.switchView(viewId); 155? 156 ? }, 157? 158 ? //后退操作 159 ? back: function () { 160? 161 ? }, 162? 163 ? //view切換,傳入要顯示和隱藏的view實例 164 ? switchView: function (viewId) { 165 ? ? if (!viewId) return; 166? 167 ? ? this.loadView(viewId, function (view) { 168 ? ? ? this.lastView = this.curView; 169 ? ? ? this.curView = view; 170? 171 ? ? ? if (this.curView) this.curView.show(); 172 ? ? ? if (this.lastView) this.lastView.show(); 173? 174 ? ? }); 175 ? } 176? 177 }); 結語
今天,我們一起分析了全局控制器app應該做些什么,并且整理了下基本思路,那么我們這個星期的主要目的便是實現這個app,今日到此結束。
本文轉自葉小釵博客園博客,原文鏈接:http://www.cnblogs.com/yexiaochai/p/3764376.html,如需轉載請自行聯系原作者
總結
以上是生活随笔為你收集整理的【单页应用】全局控制器app应该干些什么?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: make 学习体会(一)
- 下一篇: 九年双11云化架构演进和升级,打造更加完