MVC:控制器controller和狀態(tài)切換(未完成)

主題:如何使用控制器模式在客戶端保持一個(gè)狀態(tài)

包括以下分支:

1.如何將邏輯封裝成模塊蜒车,阻止全局命名空間的污染
2.如何使用視圖來(lái)進(jìn)一步簡(jiǎn)化控制器的結(jié)構(gòu)勃刨,以及怎樣在視圖中實(shí)現(xiàn)DOM事件監(jiān)聽
3.路由怎么選擇,包括使用URL中的hash片段谒养,使用新的HTML5 History API等技術(shù)挺狰,以及確保解釋兩種方法的利弊


1.如何將邏輯封裝成模塊明郭,阻止全局命名空間的污染

其實(shí)就是使用自執(zhí)行匿名函數(shù)。

相關(guān)鏈接 Self-Executing Anonymous Functions

例子:

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

根據(jù)鏈接里的文章丰泊,自執(zhí)行匿名函數(shù)可以全局導(dǎo)入薯定,即將全局對(duì)象作為參數(shù)傳入。

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

也可全局導(dǎo)出瞳购,有兩種方式:

第一種话侄,傳入window全局對(duì)象

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

第二種学赛,用一個(gè)全局對(duì)象的this

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

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

小結(jié):通過使用自執(zhí)行匿名函數(shù)可以達(dá)到模塊化的目的


2.如何使用視圖來(lái)進(jìn)一步簡(jiǎn)化控制器的結(jié)構(gòu)年堆,以及怎樣在視圖中實(shí)現(xiàn)DOM事件監(jiān)聽

為了實(shí)現(xiàn)事件回調(diào)函數(shù),需要處理上下文問題盏浇。上下文問題是指在JS里每次創(chuàng)建函數(shù)变丧,這個(gè)函數(shù)的引用都是window,即this指向的window全局绢掰,而嵌套的事件函數(shù)需要操作的卻又是上級(jí)函數(shù)的引用痒蓬,即上級(jí)函數(shù)的this,這當(dāng)然就引起了矛盾滴劲。

比如:

(function () {
   assertEqual(this, window); //  相等攻晒,即函數(shù)的this = window
}) ();

所以需要處理上下文,即處理事件函數(shù)的this指向班挖。如果想要自定義作用域的上下文炎辨,需要將函數(shù)寫入一個(gè)對(duì)象中,比如:

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

這樣xxx的作用域聪姿,即this就指向的mod對(duì)象碴萧。

然后怎么用視圖來(lái)簡(jiǎn)化控制器的結(jié)構(gòu)能?

這里使用全局this末购,而不是傳入window破喻,來(lái)抽象出控制器庫(kù),方便控制器的復(fù)用盟榴。

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 ) ;

補(bǔ)上用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);

下面則是用控制器庫(kù)的API:Controller.create()來(lái)創(chuàng)建并實(shí)例化每個(gè)具體對(duì)應(yīng)視圖元素的控制器曹质。

這里注意用jQuery.ready()的簡(jiǎn)寫jQuery(function($) {...})來(lái)確保控制器是在DOM渲染完成后才被加載的擎场。相當(dāng)于window.onload的功能

jQuery(function($) {
  var ToggleView = Controller.create({
    init: function (view) {
      this.view = $(view);
      // 下面兩項(xiàng)是jQuery的事件函數(shù)羽德,因此會(huì)給回調(diào)函數(shù)傳入event參數(shù),即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的事件函數(shù)
    }
  });

  // 實(shí)例化控制器迅办,即調(diào)用上面定義的宅静,被result繼承的init()
  new ToggleView("#view");
});

這里就是一個(gè)視圖對(duì)應(yīng)一個(gè)控制器(具體的代碼體現(xiàn)是最后的new ToggleView并傳入("#view")參數(shù) ),而一個(gè)控制器包含一個(gè)或幾個(gè)相應(yīng)事件站欺,因此也就是一個(gè)視圖對(duì)應(yīng)一個(gè)或幾個(gè)事件姨夹。

有一個(gè)好處是纤垂,這個(gè)視圖#view綁定了這個(gè)controller,如果后續(xù)要在這個(gè)controller里對(duì)視圖元素查找(可以用$("#view".find("xxx")方法)則限制在#view之下磷账,不會(huì)全DOM都查找一遍峭沦,從而提高了查找速度。

再一個(gè)示例逃糟,視圖#user的對(duì)應(yīng)控制器:

      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");
      });

這個(gè)被傳入的是ID (#users)吼鱼,然后用jQuery的選擇器獲取(this.el = $(element);)绰咽。那么到這都有個(gè)問題菇肃,視圖元素、選擇器selector(對(duì)應(yīng)有事件)不多還好剃诅,可一旦語(yǔ)義不明顯的選擇器很多就會(huì)顯得很亂巷送。

因此這個(gè)控制器的不一樣在于開辟了一個(gè)空間專門存放選擇器selector到一個(gè)變量的映射表(推薦的寫法)驶忌,這個(gè)映射表的實(shí)現(xiàn)則是基于示例里私有的兩個(gè)函數(shù)$和refreshElemments矛辕。映射表的作用是,在實(shí)例化controller之后付魔,就可以用this.xxx(對(duì)應(yīng)的選擇器變量名)代替選擇器名了聊品,而變量名則可以用語(yǔ)義更清晰的名字,對(duì)代碼閱讀更有幫助几苍,因此可以讓代碼更簡(jiǎn)潔易讀(之后還有對(duì)事件的映射翻屈,創(chuàng)建相應(yīng)映射表和映射函數(shù),效果相同)妻坝。

tips:注意是選擇器名稱在前伸眶,對(duì)應(yīng)的變量名在后,這樣在映射時(shí)才能正確對(duì)應(yīng)(事件映射相同)

(待添加事件映射示例)


狀態(tài)機(jī)

mvc結(jié)合狀態(tài)機(jī)在某一對(duì)象有多種狀態(tài)且經(jīng)常需要轉(zhuǎn)換的時(shí)候刽宪,使用狀態(tài)機(jī)實(shí)現(xiàn)非常方便厘贼。在model層給對(duì)象添加狀態(tài)機(jī)組件,然后在觸發(fā)某種狀態(tài)時(shí)(onstart,onready,onrun…)分發(fā)事件圣拄,然后再view層監(jiān)聽此事件嘴秸,當(dāng)model處于某種狀態(tài)時(shí)篡殷,觸發(fā)相應(yīng)的事件怠噪,view層監(jiān)聽到事件后做出不同的動(dòng)作映企。關(guān)于mvc歧譬、狀態(tài)機(jī)的使用可以查看sample下的demo

完整示例:狀態(tài)機(jī)完整示例代碼

狀態(tài)機(jī)本質(zhì)上由兩部分組成:狀態(tài)和轉(zhuǎn)換器次坡。

它只有一個(gè)活動(dòng)狀態(tài)爱只,也包含很多非活動(dòng)狀態(tài)张咳。當(dāng)活動(dòng)狀態(tài)之間相互切換時(shí)就會(huì)調(diào)用狀態(tài)轉(zhuǎn)換器鼎俘。

狀態(tài)機(jī)的工作場(chǎng)景:

存在兩個(gè)視圖寞肖,他們的存在是互斥關(guān)系剖煌,其中一個(gè)顯示時(shí)材鹦,另一個(gè)就是隱藏的。比如聯(lián)系人列表耕姊,一個(gè)視圖用來(lái)顯示聯(lián)系人桶唐,一個(gè)視圖用來(lái)編輯聯(lián)系人。這個(gè)場(chǎng)景就適合引入狀態(tài)機(jī)茉兰。

封裝jQuery的的綁定和觸發(fā)函數(shù):

// jQuery的綁定和觸發(fā)函數(shù)
$(".class").bind("frob.widget", function(event, dataNumber) { 
    console.log(dataNumber)  // => 5
});

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

封裝成綁定和觸發(fā)狀態(tài)機(jī):

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調(diào)用是因?yàn)橛胊pply傳入了當(dāng)前的引用(this.o)的話尤泽,在后續(xù)的事件調(diào)用就解決了上下文問題,不用再使用proxy函數(shù)规脸,或者var that = this坯约。

然后創(chuàng)建StateMachine類,主要包含一個(gè)add()函數(shù):

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

// 為StateMachine的實(shí)例添加Events莫鸭,綁定和觸發(fā)的封裝函數(shù)
$.extend(StateMachine.fn, Events);

// 再為StateMachine的實(shí)例添加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);
};

其實(shí)這個(gè)狀態(tài)機(jī)本質(zhì)上就是發(fā)布/訂閱模型的具體應(yīng)用闹丐。

add()函數(shù)就是訂閱,active()函數(shù)就是發(fā)布被因,當(dāng)調(diào)用active()時(shí)卿拴,就會(huì)發(fā)布(觸發(fā))控制器的change事件,并且傳入控制器(controller)自己本身作為回調(diào)事件的數(shù)據(jù)參數(shù)(event的后面一個(gè)參數(shù):current)梨与。

狀態(tài)機(jī)目的:如前面說的堕花,控制多個(gè)應(yīng)該互斥顯示的controller之間的激活和非激活狀態(tài),確保controller之間的存在是互斥的粥鞋,一個(gè)controller顯示了缘挽,另一個(gè)就變成非激活狀態(tài),然后消失呻粹。

狀態(tài)機(jī)用法

現(xiàn)在有兩個(gè)互斥的controller壕曼,各自包含兩個(gè)激活和未激活的函數(shù):

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

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

然后實(shí)例化一個(gè)狀態(tài)機(jī);

var sm = new StateMachine;

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

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

現(xiàn)在要激活con1的狀態(tài)等浊,則:

con1.activate():
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末腮郊,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子凿掂,更是在濱河造成了極大的恐慌伴榔,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,627評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件庄萎,死亡現(xiàn)場(chǎng)離奇詭異踪少,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)糠涛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,180評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門援奢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人忍捡,你說我怎么就攤上這事集漾∏星” “怎么了?”我有些...
    開封第一講書人閱讀 169,346評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵具篇,是天一觀的道長(zhǎng)纬霞。 經(jīng)常有香客問我,道長(zhǎng)驱显,這世上最難降的妖魔是什么诗芜? 我笑而不...
    開封第一講書人閱讀 60,097評(píng)論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮埃疫,結(jié)果婚禮上伏恐,老公的妹妹穿的比我還像新娘。我一直安慰自己栓霜,他們只是感情好翠桦,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,100評(píng)論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著胳蛮,像睡著了一般销凑。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上鹰霍,一...
    開封第一講書人閱讀 52,696評(píng)論 1 312
  • 那天闻鉴,我揣著相機(jī)與錄音茵乱,去河邊找鬼茂洒。 笑死,一個(gè)胖子當(dāng)著我的面吹牛瓶竭,可吹牛的內(nèi)容都是我干的督勺。 我是一名探鬼主播,決...
    沈念sama閱讀 41,165評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼斤贰,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼智哀!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起荧恍,我...
    開封第一講書人閱讀 40,108評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤瓷叫,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后送巡,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體摹菠,經(jīng)...
    沈念sama閱讀 46,646評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,709評(píng)論 3 342
  • 正文 我和宋清朗相戀三年骗爆,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了次氨。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,861評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡摘投,死狀恐怖煮寡,靈堂內(nèi)的尸體忽然破棺而出虹蓄,到底是詐尸還是另有隱情,我是刑警寧澤幸撕,帶...
    沈念sama閱讀 36,527評(píng)論 5 351
  • 正文 年R本政府宣布薇组,位于F島的核電站,受9級(jí)特大地震影響坐儿,放射性物質(zhì)發(fā)生泄漏体箕。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,196評(píng)論 3 336
  • 文/蒙蒙 一挑童、第九天 我趴在偏房一處隱蔽的房頂上張望累铅。 院中可真熱鬧,春花似錦站叼、人聲如沸娃兽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,698評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)投储。三九已至,卻和暖如春阔馋,著一層夾襖步出監(jiān)牢的瞬間玛荞,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,804評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工呕寝, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留勋眯,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,287評(píng)論 3 379
  • 正文 我出身青樓下梢,卻偏偏與公主長(zhǎng)得像客蹋,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子孽江,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,860評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容