漫談做坊,MVC,MVVM及FLUX(1)

篇首語:這將是一系列文章,按心情更新,從做坊開始,簡述在前端開發中遇到的一系列問題,以及爲了解決相似問題而衍生出的設計模式,看看這些模式是如何解決咱們遇到的難題以及相應的好處和不足。html

做坊模式

在一個傳統的做坊模式下的應用,大概是這樣的。前端

需求:一個TodoList。設計模式

  • 能夠增長和刪除條目

Stage 1

代碼大體是以下,具體結果能夠參照我建立的JsFiddle http://jsfiddle.net/xykhzwq8/服務器

var ndContent = $("#content");
 var ndList = $("#todoList");

  // 增長一個item
  $("#add").click(function(){
      var content = ndContent.val();

      $(['<li class="item">',
         '<span class="item-content">',
           content,
         '</span>',
        '<button class="del">x</button>',
       '</li>'
       ].join('')
       ).appendTo(ndList);
  });

  // 刪除一個item
  ndList.on('click', '.del', function(){
    $(this).parents('.item').remove();
  });

這是極簡單的,並且也看不到做坊模式的很差的地方。相反,它看上去很易懂,很直接,咱們都很喜歡。app

可是因爲它實在太簡單,知足不了用戶的需求,用戶但願能夠:dom

  • 勾選已完成的和未完成的
  • 顯示已完成的數量和未完成的數量

因而咱們開始修改Dom結構,大體以下:ide

<li class="item">
   <input type="checkbox" class="check"/>
   <span> {content} </span>
   <button class="del">x</button>
</li>

以及一個顯示總數量已完成數量未完成數量的統計框。測試

<div id="stat">
    <span>已完成: <i class="finished">{finished}</i></span>
    <span>未完成: <i class="unfinished">{unfinished}</i></span>
    <span>總量: <i class="total">{total}</i></span>
</div>

新增了一個checkbox用於讓用戶勾選,同時JS代碼擴展以下:
http://jsfiddle.net/b7s2z2jw/2/網站

ndList.on("click", ".check", function(){
    updateStat();
});

function updateStat(){
    var finished = 0;
    var totalCount = 0;
    var unfinished = 0
    ndList.find('.check').each(function(index, checkbox) {
        totalCount++;
        if (checkbox.checked) finished++;
    });
    unfinished = totalCount - finished;

    $(".finished").html(finished);
    $(".unfinished").html(unfinished);
    $(".total").html(totalCount);
}

依然很是容易實現。但注意了,咱們增長了一個updateStat的方法,而且須要在四個地方顯調用它:this

  • 點擊勾選時(正如上面代碼所示)
  • 應用啓動時
  • 點擊增長按鈕時
  • 點擊刪除按鈕時

除了點擊勾選是新增的需求外,其他三項均是原有的代碼和邏輯。這裏展現了做坊模式的一個問題是:

當有新業務(本例是統計數量)進入時,須要修改已有的業務(啓動,增長,刪除)的代碼

以上是咱們遇到的第一個問題。注意,在這個例子中或許還看不出太大的不便,當一個應用有着更多的交互和數據時:

  • 修改任何已有的代碼都是危險的。
  • 容易忘記或忽略在相應的場景增長新來業務的代碼。
  • 業務之間存在強耦合,將致使難以維護。

Stage 2

許多用戶但願:

  • 每個item均可以彈出窗口編輯

因而咱們遇到了不一樣UI塊間交互的問題,一個簡陋的解決方式是這樣的:

首先升級一下item節點的內容,增長一個edit的按鈕:

<li class="item">
   <input type="checkbox" class="check"/>
   <span class="item-content"> tv </span>
   <button class="del">x</button>
   <button class="edit">edit</button>
</li>

接下來處理交互,代碼大體爲:
http://jsfiddle.net/dht27Lqe/2/

ndList.on('click', '.edit', function(){
    var item = $(this).parents('.item').find('.item-content');
    var content = item.html();
    ndMain.hide();
    $("#edit-dialog .content").val(content);
    $("#edit-dialog").show();
    $("#edit-dialog .ok").one('click', function(){
        var content = $("#edit-dialog .content").val();
        item.html(content);
        $("#edit-dialog").hide();
        ndMain.show();
    });
});

$("#edit-dialog .cancel").click(function(){
   ndMain.show();
   $("#edit-dialog").hide();
});

上述代碼有點醜陋,可是能工做,即使是在做坊模式下,咱們也能夠將edit-dialog封裝代組件以減小對其的dom操做,如:

ndList.on('click', '.edit', function(){
    var item = $(this).parents('.item').find('.item-content');
    var content = item.html();

     $dialog.open({
        content: content,
        onOk: function (content) {
           item.html(content);
        }
     });
});

可是在上面兩個代碼片斷中,咱們都能看到一段無比醜陋的代碼:

var item = $(this).parents('.item').find('.item-content');

它的做用是,根據當前點擊的edit按鈕,獲取其對應item的內容。這段代碼強耦合了HTML的片斷,其帶來的災難是另每一個前端在開發業務時都極爲頭疼:

當UI變化時,如PM但願改變item的外觀,不少時候會無可避免地改變一個item的HTML,致使你:

  • 不敢輕易修改一個標籤的class
  • 不敢輕易的調整內部結構
  • 一旦有新的需求改變外觀,將產生極大的調試包袱,實際工程中,你甚至不知道你的改動會產生bug

這即是咱們遇到的第二個問題:

業務邏輯與HTML結構強耦合,修改其中之一,必須當心翼翼地調試看上去與其徹底不相干的另外一個。(不少狀況下你甚至找不到另外一個在哪...
分離展現交互彷佛成了笑話,由於它帶來更大的不肯定性和不穩定的依賴耦合。

Stage 3

公司業務增加了,TodoList將全面升級,針對不一樣用戶推出了以下不一樣的功能:

  • 高級用戶能夠點擊全選按鈕,將全部事務設置爲已讀

對於全選按鈕,其HTML爲:

<input type='checkbox' class='checkall' />

這段HTML僅在用戶是高級用戶時,纔會從服務器渲染下來。因而在JS中須要:

  • 判斷這個節點是否存在
  • 處理全選時產生的操做

代碼以下:

var ndCheckAll = $('.checkall');

// 若是這個節點存在
if (ndCheckAll[0]) {
  ndCheckAll.click(function(){
    // 將全部的checkbox設爲true
    ndList.find('item .check').attr("checked",true);
    // 更新底部的統計狀態
    updateStat();
  });
}

// 修改點擊每一個item的checkbox的代碼
ndList.on('.check', 'click', function(){
    // 保留原來的代碼
     .....

   // 新增的代碼
   // 1. 若是點擊後,列表狀態不是全選,那麼將.checkedAll設爲false
   // 2. 若是點擊後,列表狀態爲全選,那麼將.checkedAll設爲true
   // 3. 還須要判斷.checkedAll這個節點是否存在


});

上面的代碼存在的問題是:

  • 新增業務在完成本職任務外,還須要調用updateStat,它本不該該關注這個的。
  • 須要修改已有代碼來適配新增的業務邏輯。
  • 頁面經過判斷節點的存在來選擇性執行某段代碼,這是災難性的。
    >若是有n個用戶狀態的話,那麼產生的頁面分支組合就是n*(n-1)/2種,其相互依賴耦合幾乎是不可維護的。
    > 設想在本例中,若是底部狀態欄也是由頁面根據用戶等級來判斷是否顯示的時候,那麼在全選業務代碼中調用updateStat是什麼意思?每新增一個需求,都要對已有的狀況有徹底掌控,這負擔是極重的。

這種狀況存在很廣泛,以電商網站下單爲例,一個訂單根據其不一樣屬性在下單時可能須要展現:

  • 物流信息
  • 積分選擇
  • 優惠券選擇
  • 滿xxx減yy
  • 驗證手機號
  • ......

這些都將產生不一樣形態的組合和依賴,改變任何一段的信息,都將可能改變其它地方的展現。

階段性總結

做坊模式存在的問題:

  • 沒有數據模型,一切操做都在對DOM節點的讀取,不能輕易修改DOM節點結構和屬性
  • 沒有職責分離,每個操做都須要本身負責更新潛在影響的地方,給可維護性帶來了問題
  • 新增業務須要修改已有代碼
  • 新增業務須要插入與其間接相關的已有代碼
  • 每個區塊盤根錯節的依賴,致使代碼沒法進行測試

在實際的做坊工程中,因爲人們習慣於操做DOM,還可能遇到以下坑爹的問題:

  • 在引用獨立組件時,肆意地取其內部節點狀態,致使頁面強依賴組件的內部實現,而組件的維護者並不知道哪些頁面在使用,一旦更新內部實現,頁面掛掉。
相關文章
相關標籤/搜索