MVC:控制器controller和状态切换(未完成)

主题:如何使用控制器模式在客户端保持一个状态

包括以下分支:

1.如何将逻辑封装成模块,阻止全局命名空间的污染
2.如何使用视图来进一步简化控制器的结构,以及怎样在视图中实现DOM事件监听
3.路由怎么选择,包括使用URL中的hash片段,使用新的HTML5 History API等技术,以及确保解释两种方法的利弊


1.如何将逻辑封装成模块,阻止全局命名空间的污染

其实就是使用自执行匿名函数。

相关链接 Self-Executing Anonymous Functions

例子:

(function(){
  console.log('Hello World!');
})();

根据链接里的文章,自执行匿名函数可以全局导入,即将全局对象作为参数传入。

(function ($) {
    / ..... / 
}) (jQuery);

也可全局导出,有两种方式:

第一种,传入window全局对象

(function ($, exports) {
    / ..... 
    exports.xxx = xxx;
    / 
}) (jQuery, window);

第二种,用一个全局对象的this

var exports = this;   // exports被赋值全局的this

(function ($) {
    / ..... 
    var xxx = {};
    xxx.create = function () {
        / .... / 
    };
    exports.xxx = xxx;
    / 
}) (jQuery);

小结:通过使用自执行匿名函数可以达到模块化的目的


2.如何使用视图来进一步简化控制器的结构,以及怎样在视图中实现DOM事件监听

为了实现事件回调函数,需要处理上下文问题。上下文问题是指在JS里每次创建函数,这个函数的引用都是window,即this指向的window全局,而嵌套的事件函数需要操作的却又是上级函数的引用,即上级函数的this,这当然就引起了矛盾。

比如:

(function () {
   assertEqual(this, window); //  相等,即函数的this = window
}) ();

所以需要处理上下文,即处理事件函数的this指向。如果想要自定义作用域的上下文,需要将函数写入一个对象中,比如:

(function () {
    var mod = {};
    
    mod.xxx = function () {
        / ... /
    };
}) ();

这样xxx的作用域,即this就指向的mod对象。

然后怎么用视图来简化控制器的结构能?

这里使用全局this,而不是传入window,来抽象出控制器库,方便控制器的复用。

var exports = this;

(function ($) {
    var mod = {};
    
    mod.create = function (includes) {
        var result = function () {
            this.init.apply(this, arguments);
        };
 
        result.fn = result.prototype;
        result.fn.init = function() {};
        
        result.proxy = function(func) { return $.proxy(func, this); );
        result.fn.proxy = result.proxy;

        result.include = function(ob) { $.extend(this.fn, ob); };
        result.extend = function(ob) { $.extend(this, ob); };
        if (includes) {
            result.include(includes);
        };

        exports.Controller = mod;
    };
}) ( jQuery ) ;

补上用window的写法:

      (function ($, exports) {
        var mod = {};

        mod.create = function (includes) {
          var result = function () {
            this.init.apply(this, arguments);
          };

          result.fn = result.prototype;
          result.fn.init = function () {};

          result.proxy = function (func) {
            return $.proxy(func, this);
          };
          result.fn.proxy = result.proxy;
          result.include = function (ob) {
            $.extend(this.fn, ob);
          };
          result.extend = function (ob) {
            $.extend(this, ob);
          };
          if (includes) {
            result.include(includes)
          };

          return result;
        };

        exports.Controller = mod;
      })(jQuery, window);

下面则是用控制器库的API:Controller.create()来创建并实例化每个具体对应视图元素的控制器。

这里注意用jQuery.ready()的简写jQuery(function($) {...})来确保控制器是在DOM渲染完成后才被加载的。相当于window.onload的功能

jQuery(function($) {
  var ToggleView = Controller.create({
    init: function (view) {
      this.view = $(view);
      // 下面两项是jQuery的事件函数,因此会给回调函数传入event参数,即e
      this.view.mouseover(this.proxy(this.toggleClass), true); 
      this.view.mouseout(this.proxy(this.toggleClass), false);
    },

    this.toggleClass: function(e) {
      this.view.toggleClass("over", e.data); // 这里是jQuery的事件函数
    }
  });

  // 实例化控制器,即调用上面定义的,被result继承的init()
  new ToggleView("#view");
});

这里就是一个视图对应一个控制器(具体的代码体现是最后的new ToggleView并传入("#view")参数 ),而一个控制器包含一个或几个相应事件,因此也就是一个视图对应一个或几个事件。

有一个好处是,这个视图#view绑定了这个controller,如果后续要在这个controller里对视图元素查找(可以用$("#view".find("xxx")方法)则限制在#view之下,不会全DOM都查找一遍,从而提高了查找速度。

再一个示例,视图#user的对应控制器:

      var exports = this;

      jQuery(function ($) {
        exports.SearchView = Controller.create({
          elements: {
            "input[type=search]": "searchInput",
            "form": "searchForm"
          },

          init: function (element) {
            this.el = $(element);
            this.refreshElements();
            this.searchForm.submit(this.proxy(this.search));
          },

          search: function (e) {
            alert("Searching: " + this.searchInput.val());
            return false;
          },

          // 私有
          $: function (selector) {
            return $(selector, this.el);
          },

          refreshElements: function () {
            for (var key in this.elements) {
              this[this.elements[key]] = this.$(key);
            }
          }
        });

        new SearchView("#users");
      });

这个被传入的是ID (#users),然后用jQuery的选择器获取(this.el = $(element);)。那么到这都有个问题,视图元素、选择器selector(对应有事件)不多还好,可一旦语义不明显的选择器很多就会显得很乱。

因此这个控制器的不一样在于开辟了一个空间专门存放选择器selector到一个变量的映射表(推荐的写法),这个映射表的实现则是基于示例里私有的两个函数$和refreshElemments。映射表的作用是,在实例化controller之后,就可以用this.xxx(对应的选择器变量名)代替选择器名了,而变量名则可以用语义更清晰的名字,对代码阅读更有帮助,因此可以让代码更简洁易读(之后还有对事件的映射,创建相应映射表和映射函数,效果相同)。

tips:注意是选择器名称在前,对应的变量名在后,这样在映射时才能正确对应(事件映射相同)

(待添加事件映射示例)


状态机

mvc结合状态机在某一对象有多种状态且经常需要转换的时候,使用状态机实现非常方便。在model层给对象添加状态机组件,然后在触发某种状态时(onstart,onready,onrun…)分发事件,然后再view层监听此事件,当model处于某种状态时,触发相应的事件,view层监听到事件后做出不同的动作。关于mvc、状态机的使用可以查看sample下的demo

完整示例:状态机完整示例代码

状态机本质上由两部分组成:状态和转换器。

它只有一个活动状态,也包含很多非活动状态。当活动状态之间相互切换时就会调用状态转换器。

状态机的工作场景:

存在两个视图,他们的存在是互斥关系,其中一个显示时,另一个就是隐藏的。比如联系人列表,一个视图用来显示联系人,一个视图用来编辑联系人。这个场景就适合引入状态机。

封装jQuery的的绑定和触发函数:

// jQuery的绑定和触发函数
$(".class").bind("frob.widget", function(event, dataNumber) { 
    console.log(dataNumber)  // => 5
});

$(".class").trigger("frob.widget", 5);

封装成绑定和触发状态机:

var Events = {
    bind: function () {
      if (!this.o) {
        this.o = $({});
      }
      this.o.bind.apply(this.o, arguments);
    },

    trigger: function () {
      if (!this.o) {
        this.o = $({});
      }
      this.o.trigger.apply(this.o, arguments);
    }
};

注意bind和trigger都使用apply调用是因为用apply传入了当前的引用(this.o)的话,在后续的事件调用就解决了上下文问题,不用再使用proxy函数,或者var that = this。

然后创建StateMachine类,主要包含一个add()函数:

var StateMachine = function() {};
StateMachine.fn = StateMachine.prototype;

// 为StateMachine的实例添加Events,绑定和触发的封装函数
$.extend(StateMachine.fn, Events);

// 再为StateMachine的实例添加add()
StateMachine.fn.add = function (controller) {
  this.bind("change", function(e, current) {
    if (controller == current) {
      controller.activate();
    } else {
      controller.deactivate();
    }
  });

  controller.active = $.proxy(function() {
    this.trigger("change", controller);
  }, this);
};

其实这个状态机本质上就是发布/订阅模型的具体应用。

add()函数就是订阅,active()函数就是发布,当调用active()时,就会发布(触发)控制器的change事件,并且传入控制器(controller)自己本身作为回调事件的数据参数(event的后面一个参数:current)。

状态机目的:如前面说的,控制多个应该互斥显示的controller之间的激活和非激活状态,确保controller之间的存在是互斥的,一个controller显示了,另一个就变成非激活状态,然后消失。

状态机用法

现在有两个互斥的controller,各自包含两个激活和未激活的函数:

var con1 = {
  activate: function() { /* ... */ },
  deactivate: function() { /* ... */ }
};

var con2 = {
  activate: function() { /* ... */ },
  deactivate: function() { /* ... */ }
};

然后实例化一个状态机;

var sm = new StateMachine;

然后用add方法添加con1和con2。

sm.add(con1);
sm.add(con2);

现在要激活con1的状态,则:

con1.activate():
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,723评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,080评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,604评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,440评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,431评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,499评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,893评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,541评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,751评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,547评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,619评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,320评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,890评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,896评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,137评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,796评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,335评论 2 342

推荐阅读更多精彩内容