漫谈做坊,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,还可能遇到以下坑爹的问题:

  • 在引用独立组件时,肆意地取其内部节点状态,致使页面强依赖组件的内部实现,而组件的维护者并不知道哪些页面在使用,一旦更新内部实现,页面挂掉。
相关文章
相关标签/搜索