前言

之前,我们形成了页面片相关的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与键值全部做一个映射,比如

 var App = Backbone.Router.extend({
routes: {
"": "index", // #index
"index": "index", // #index
"detail": "detail" // #detail
},
index: function () {
var index = new Index(this.interface); },
detail: function () {
var detail = new Detail(this.interface); },
initialize: function () { },
interface: {
forward: function (url) {
window.location.href = ('#' + url).replace(/^#+/, '#');
} } });

然后整体的功能完全依赖于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,的方法了,这里先给出类图再做一一实现:

这里做初步的实现:

 "use strict";
var Application = _.inherit({ //设置默认的属性
defaultPropery: function () { //存储view队列的hash对象,这里会新建一个hash数据结构,暂时不予理睬
this.views = new _.Hash(); //当前view
this.curView; //最后访问的view
this.lastView; //各个view的映射地址
this.viewMapping = {}; //本地维护History逻辑
this.history = []; //是否开启路由监控
this.isListeningRoute = false; //view的根目录
this.viewRootPath = 'app/views/'; //当前对应url请求
this.request = {}; //当前对应的参数
this.query = {}; //pushState的支持能力
this.hasPushState = !!(this.history && this.history.pushState); //由用户定义的获取viewid规则
this.getViewIdRules = function (url, hasPushState) {
return _.getUrlParam(url, 'viewId');
}; }, //@override
handleOptions: function (opts) {
_.extend(this, opts);
}, initialize: function (opts) { this.defaultPropery();
this.handleOptions(opts); //构造系统各个事件
this.buildEvent(); //首次动态调用,生成view
this.start();
}, buildEvent: function () {
this._requireEvent();
this._routeEvent();
}, _requireEvent: function () {
requirejs.onError = function (e) {
if (e && e.requireModules) {
for (var i = 0; i < e.requireModules.length; i++) {
console.log('抱歉,当前的网络状况不给力,请刷新重试!');
break;
}
}
};
}, //路由相关处理逻辑,可能是hash,可能是pushState
_routeEvent: function () { //默认使用pushState逻辑,否则使用hashChange,后续出pushState的方案
$(window).bind('hashchange', _.bind(this.onURLChange, this)); }, //当URL变化时
onURLChange: function () {
if (!this.isListeningRoute) return; }, startListeningRoute: function () {
this.isListeningRoute = true;
}, stopListeningRoute: function () {
this.isListeningRoute = false;
}, //解析的当前url,并且根据getViewIdRules生成当前viewID
parseUrl: function (url) { }, //入口点
start: function () {
var url = decodeURIComponent(window.location.hash.replace(/^#+/i, '')).toLowerCase();
this.history.push(window.location.href);
//处理当前url,会将viewid写入request对象
this.parseUrl(url); var viewId = this.request.viewId; //首次不会触发路由监听,直接程序导入
this.switchView(viewId); }, //根据viewId判断当前view是否实例化
viewExist: function (viewId) {
return this.views.exist(viewId);
}, //根据viewid,加载view的类,并会实例化
//注意,这里只会返回一个view的实例,并不会显示或者怎样,也不会执行app的逻辑
loadView: function (viewId, callback) { //每个键值还是在全局views保留一个存根,若是已经加载过便不予理睬
if (this.viewExist(viewId)) {
_.callmethod(callback, this, this.views.get(viewId));
return;
} requirejs([this._buildPath(viewId)], $.proxy(function (View) {
var view = new View(); this.views.push(viewId, view); //将当前view实例传入,执行回调
_.callmethod(callback, this, view); }, this));
}, //根据viewId生成路径
_buildPath: function (viewId) {
return this.viewMapping[viewId] ? this.viewMapping[viewId] : this.viewRootPath + viewId;
}, //注意,此处的url可能是id,也可能是其它莫名其妙的,这里需要进行解析
forward: function (viewId) { //解析viewId逻辑暂时省略
//......
this.switchView(viewId); }, //后退操作
back: function () { }, //view切换,传入要显示和隐藏的view实例
switchView: function (viewId) {
if (!viewId) return; this.loadView(viewId, function (view) {
this.lastView = this.curView;
this.curView = view; if (this.curView) this.curView.show();
if (this.lastView) this.lastView.show(); });
} });

结语

今天,我们一起分析了全局控制器app应该做些什么,并且整理了下基本思路,那么我们这个星期的主要目的便是实现这个app,今日到此结束。

【单页应用】全局控制器app应该干些什么?的更多相关文章

  1. 单页Html及Android App供小孩学习常用汉字

    为了检验及帮助小孩学习常用汉字,简单开发本网页应用: 常用汉字是按使用频率排序的,来源于网上: 该简单应用 有Android APP下载 “学习常用汉字_20150910.apk” 单页Html 示例 ...

  2. hbuilder+vue单页应用打包成APP后退按钮返回上一页的问题

    APP打包工具:hbuilder 需要js包:mui.js ,引入方法https://www.cnblogs.com/v616/p/11290281.html 实现原理:在vue根组件App.vue监 ...

  3. Singal Page App:使用Knockout和RequireJS创建高度模块化的单页应用引擎

    Singal Page App 开篇扯淡 距离上一篇文章已经有好几个月,也不是没有时间记录点东西,主要是换了新的工作,在一家外资工作,目前的工作内容大多都是前端开发,新接触的东西因为时间原因,大多还不 ...

  4. idea 中全局查找不到文件 (两shift),单页搜索不到关键字的原因

    全局查找不到文件是因为把要找的目录的本级或者上级设置为了额外的,所以自然找不到 而单页搜索不到内容是因为设置了words关键字,这个要全部都输入完才能找到(也就是整个关键字进行匹配,匹配到了整体才会查 ...

  5. 单页应用(SPA,Single-page-App)和多页应用(MPA,Multi-page App)的区别

    单页应用(SPA,Single-page-App)和多页应用(MPA,Multi-page App)的区别 参考博客:https://www.jianshu.com/p/4c9c29967dd6

  6. 【再探backbone04】单页应用的基石-路由处理

    前言 首先发一点牢骚,互联网公司变化就是快,我去一个团队往往就一年时间该团队就得解散,这不,公司居然把无线团队解散了,我只能说,我那个去!!! 我去年还到处让人来呢,一个兴兴向荣的团队说没就没了啊!我 ...

  7. Nodejs之MEAN栈开发(六)---- 用Angular创建单页应用(上)

    在上一节中我们学会了如何在页面中添加一个组件以及一些基本的Angular知识,而这一节将用Angular来创建一个单页应用(SPA).这意味着,取代我们之前用Express在服务端运行整个网站逻辑的方 ...

  8. Web API 2 入门——使用ASP.NET Web API和Angular.js构建单页应用程序(SPA)(谷歌翻译)

    在这篇文章中 概观 演习 概要 由网络营 下载网络营训练包 在传统的Web应用程序中,客户机(浏览器)通过请求页面启动与服务器的通信.然后,服务器处理请求,并将页面的HTML发送给客户端.在与页面的后 ...

  9. vue单页(spa)前端git工程拆分实践

    背景 随着项目的成长,单页spa逐渐包含了许多业务线 商城系统 售后系统 会员系统 ... 当项目页面超过一定数量(150+)之后,会产生一系列的问题 可扩展性 项目编译的时间(启动server,修改 ...

随机推荐

  1. SQL 中 EXISTS 与 NOT EXISTS

    带有 EXISTS 操作符的子查询不返回任何数据,只产生逻辑真值 'true' 或逻辑假值 'false'.带有 EXISTS 操作符的子查询都是相关子查询. 相关子查询:子查询的条件依赖父查询. E ...

  2. JAVA基础-输入输出:1.编写TextRw.java的Java应用程序,程序完成的功能是:首先向TextRw.txt中写入自己的学号和姓名,读取TextRw.txt中信息并将其显示在屏幕上。

    1.编写TextRw.java的Java应用程序,程序完成的功能是:首先向TextRw.txt中写入自己的学号和姓名,读取TextRw.txt中信息并将其显示在屏幕上. package Test03; ...

  3. memset

    函数原型: void *memset(void *s, int ch, size_t n); 函数解释:将s中当前位置后面的n个字节 (typedef unsigned int size_t )用 c ...

  4. Security7:View Usage

    一,在Database level上,主要有 sys.database_principals, sys.database_permissions 和 sys.database_role_members ...

  5. js实现多张图片每隔一秒换一张图片

    aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAAAlkAAAHWCAIAAADLlAuAAAAgAElEQVR4nOzd5XNc157w+/l7bt2n6t

  6. Android 裁剪图片为圆形图片

    转自http://blog.csdn.net/kkmike999/article/details/16359713 /** * 转换图片成圆形 * * @param bitmap * 传入Bitmap ...

  7. Jenkins+SVN+tomcat持续集成发布

    有代码更新后重新打包到tomcat再发布,是不是很烦? 看了下面的东西你就不会烦了. SVN或者git等代码版本控制工具不说了,如果是本地开发,也可以安装一个svn server端 jenkins下载 ...

  8. Microsoft Message Analyzer (微软消息分析器,“网络抓包工具 - Network Monitor”的替代品)官方正式版现已发布

    来自官方日志的喜悦 被誉为全新开始的消息分析器时代,由MMA为您开启,博客原文写的很激动,大家可以点击这里浏览:http://blogs.technet.com/b/messageanalyzer/a ...

  9. Word基础

    1.页面设置 默认大小A4,长宽比0.618 页面布局 2.字体设置 选择要设置的字体->右键->字体 3.选择性粘贴 4.段落设置 选择文字->右键->段落 5.表格 =SU ...

  10. js实现css3的过渡,需要注意的一点(浏览器优化)

    大部分浏览器对元素几何改变时候的重排做了优化.据说是这样子,一定时间内本应多次重排的改变,浏览器会hold住,仅一次重排.其中如果使用分离的一步处理过程,例如计时器,依然多次重排.例如,当我们应用tr ...