zoukankan      html  css  js  c++  java
  • 前端代码组织优化--小demo(进阶你的思路)

    1.事出必有因

      最近在看老项目的代码,一个富客户端的js代码,几千行的代码,全是function(){} var...的垂直布局,真的是要感动的哭了。

      一开始都是这样,想实现什么功能,不管三七二十一,function走起,最终堆起无数个变量和函数来完成一个画面的js。我也是,但过段时间自己去改代码bug或者加功能的时候,我的天,这是我写的吗,什么时候写的,怎么理不清思路了,而且,修改一个地方其他地方也得改,改完了还容易出新bug,偶尔都会忘了是自己写的,心里默念:这个傻X...恩,还好是默念。

      慢慢的代码看多了点,了解了些js的模块封装的一些方式,面向对象的相关思想(单一职责、高内聚低耦合....再说就有点装了>.<),越来越觉得易读、易改的代码应该需要更好的组织形式,正好最近碰到了一个网友相关的问题,看了他想优化的代码,真有看到自己一开始写的代码的感觉:各处填补想完美解决问题,可最后还是会有出乎意料的bug,于是用了他的代码做了次实践。

    2.一睹为快

      如下图,功能比较简易:

      选择之后添加,展示区便陈列:

      展示区点击‘X’的时候去除当前内容,选择区相应也取消对应的勾选:

    3.初出茅庐

    先上原版代码看看:

    <!DOCTYPE html>
     <html>
     
     <head>
         <meta charset="UTF-8">
         <title>多选框问题</title>
     </head>
     
     <body>
         <!--<input type="text" data-bind-content="name" />
             <span data-bind-content='name'></span>-->
     
         <h4>选择区</h4>
         <div>
             
             <ul id="ul1">
                 <li>全选<input type="checkbox" name="checkall" /></li>
                 <li><input type="checkbox" name="checkthis" /><span>1</span></li>
                 <li><input type="checkbox" name="checkthis" /><span>2</span></li>
                 <li><input type="checkbox" name="checkthis" /><span>3</span></li>
                 <li><input type="checkbox" name="checkthis" /><span>4</span></li>
                 <li><input type="checkbox" name="checkthis" /><span>5</span></li>
             </ul>
         </div>
         
         <button id="add">添加</button>
         <h4>展示区</h4>
         <ul id="ul2"></ul>
     </body>
     <script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
     <script type="text/javascript">
         //封装
         var checkBox = (function () {
             var globalV = [];
             var yourChose = function (tableId, addClickId, showId) {
                 console.log($('#' + tableId + ' input[name=checkall]'));
                 //全选
                 $('#' + tableId + ' input[name=checkall]').click(function () {
                     //如果选择全选, 所有的选择框都选中,去除全选,所有的选择框去除选中    
                     if ($(this).prop('checked')) {
                         $('#' + tableId + ' input[name=checkthis]').prop('checked', true);
                         //全选的时候,将所有选框的数据取出来传给全局变量globalV
                         $('#' + tableId + ' input[name=checkthis]').each(function (i, ele) {
                             var choseDate = {};
                             choseDate.isChecked = true;
                             choseDate.id = $(ele).parent().children('span').html();
                             globalV.push(choseDate);
                         });
                     } else {
                         $('#' + tableId + ' input[name=checkthis]').prop('checked', false);
                     }
                     console.log(globalV);
                 })
                 //对各个选择框绑定事件
                 $('#' + tableId).on('change', 'input[name=checkthis]', function () {
                     var arr = [];//存储每个选择框的状态
                     var choseDate = {};//存储被选中的选择框的数据
                     //<li><input type="checkbox" name="check-this" /><span>3</span></li>获取span里面的值
                     var this_value = $(this).parent().children('span').html();
                     //遍历每个选择框取选择的状态
                     $('#' + tableId + ' input[name=checkthis]').each(function (i, ele) {
                         arr.push($(ele).prop('checked'));
                     });
                     //如果有未选中的状态,去除全选框的选中状态,否则保留添加全选框的的选中状态
                     if (arr.indexOf(false) == -1) {
                         $('#' + tableId + ' input[name=checkall]').prop('checked', true);
                     } else {
                         $('#' + tableId + ' input[name=checkall]').prop('checked', false);
                     }
                     //对应每个选择框的change事件,如果这个选择框选中,则存储这个选择框的数据,否则遍历存储数据的变量,移除这个取消选中的的选择框的数据
                     if ($(this).is(':checked')) {
                         choseDate.isChecked = true;
                         choseDate.id = this_value;
                         globalV.push(choseDate);
                     } else {
                         for (var i = 0; i < globalV.length; i++) {
                             if (this_value == globalV[i].id) {
                                 globalV.splice(i, 1);
                             }
                         }
                     }
                     console.log(globalV);
                 });
                 //点击添加按钮的事件
                 $('#'+addClickId).click(function (e) {
                     e.preventDefault();
                     $('#'+showId).empty();//清空展示区里面的内容
                     console.log(globalV);
                     //如果没有选中任何选择框,则弹出提示
                     if (globalV.length == 0) {
                         alert('请先选择!');
                     } else {
                         //如果选中了一些选择框,则全局变量数据不为空,开始遍历全局变量
                         for (var j = 0; j < globalV.length; j++) {
                             //按照全局变量globalV,给展示区创建元素;(包含了删除按钮)
                             var liElement = '<li>
                                                 <span>'+ globalV[j].id + '</span>
                                                 <p style="display:inline-block;20px;height:20px;background-color:red;border-radius:50%;text-align:center">X</p>
                                             </li>';
                             $('#'+showId).append(liElement);
                         }
                         //给删除按钮添加点击事件
                         $('#'+showId).on('click', 'p', function () {
                             //var findAndChangeState=$(this).parent('li').children('span').html();
                             //找到这个删除按钮对应的父级标签li下面的span标签的内容;注意:这个是简化;就放在了标签里面,实际情况可能是个属性,获取的这个值对应一个选择框
                             //由这个值来查找对应的选择框,从而改变选择框的状态;
                             //这里是点击了删除按钮,那么与他对应的选择框的选中状态也会被去除
                             var findAndChangeState = $(this).parent('li').children('span').html();
                             //遍历选择框找到与删除按钮对应的选择框,将其状态改为未选中,同时将全选的选择框也改为未选中
                             $('#' + tableId + ' input[name=checkthis]').each(function (i, ele) {
                                 if ($(this).parent().children('span').html() == findAndChangeState) {
                                     $(this).parent().children('input').prop('checked', false);
                                     $('#' + tableId + ' input[name=checkall]').prop('checked', false);
                                 }
                             });
                             //改完之后这个删除按钮对应的父级标签
                             $(this).parent('li').remove();
                         })
                     }
                 })
             };
             return {
                 globalV:globalV,
                 yourChose:yourChose
             }
         })()
         checkBox.yourChose('ul1', 'add', 'ul2')
     </script>
     
     </html>
    原版

      代码拷下来,看着比我刚开始写的前台代码要好不少:注释到位、封装避免全局环境污染、事件功能明确。

      放浏览器跑一遍,多点两下...bug就出来了,细看以下代码就知道,bug的出现与他定义的globalV有关,而这个值可以看到是两处在改动,而且是每次事件都会更新。一个数据多个地方多次更改,想知道怎么出问题了,肯定是要花点时间排查的。

      bug就不提了,代码是以一种非常流程化的思路在行文,该干什么了就码代码去干什么,我们都在这么干。可当时自己爽了,以后就不爽了,别人也不爽...特别是当代码量开始增大的时候。

    4.渐入佳境

    <!--
    event{
        select:fun1,
        add:fun2,
        remove:fun3,
    }
    
    mvc:
    model
    controller
    view
    -->
    <!DOCTYPE html>
    <html>
    
    <head>
        <meta charset="UTF-8">
        <title>多选框问题</title>
    </head>
    
    <body>
        <!--<input type="text" data-bind-content="name" />
            <span data-bind-content='name'></span>-->
    
        <h4>选择区</h4>
        <div>
    
            <ul id="ul1">
                <li>全选<input type="checkbox" name="checkall" /></li>
                <li><input type="checkbox" name="checkthis" /><span>1</span></li>
                <li><input type="checkbox" name="checkthis" /><span>2</span></li>
                <li><input type="checkbox" name="checkthis" /><span>3</span></li>
                <li><input type="checkbox" name="checkthis" /><span>4</span></li>
                <li><input type="checkbox" name="checkthis" /><span>5</span></li>
            </ul>
        </div>
    
        <button id="add">添加</button>
        <h4>展示区</h4>
        <ul id="ul2"></ul>
    </body>
    <script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
    <script type="text/javascript">
        //封装
        var checkBox = (function () {
    
            var globalV = [];
            var yourChose = function (tableId, addClickId, showId) {
                //负责更新数据
                var updateData = function () {
                    globalV = [];
                    $('#' + tableId + ' input[name=checkthis]').each(function () {
                        if ($(this).is(':checked')) {
                            var choseDate = {};
                            var this_value = $(this).parent().children('span').html();
                            choseDate.isChecked = true;
                            choseDate.id = this_value;
                            globalV.push(choseDate);
                        }
                    });
                }
    
                //负责更新画面
                //checkBox状态
                function fun1() {
                    if ($(this).attr("name") == "checkthis") {
                        var arr = [];//存储每个选择框的状态
                        var choseDate = {};//存储被选中的选择框的数据
                        //<li><input type="checkbox" name="check-this" /><span>3</span></li>获取span里面的值
                        var this_value = $(this).parent().children('span').html();
                        //遍历每个选择框取选择的状态
                        $('#' + tableId + ' input[name=checkthis]').each(function (i, ele) {
                            arr.push($(ele).prop('checked'));
                        });
                        //如果有未选中的状态,去除全选框的选中状态,否则保留添加全选框的的选中状态
                        if (arr.indexOf(false) == -1) {
                            $('#' + tableId + ' input[name=checkall]').prop('checked', true);
                        } else {
                            $('#' + tableId + ' input[name=checkall]').prop('checked', false);
                        }
                    } else {
                        //如果选择全选, 所有的选择框都选中,去除全选,所有的选择框去除选中
                        if ($(this).prop('checked')) {
                            $('#' + tableId + ' input[name=checkthis]').prop('checked', true);
                        } else {
                            $('#' + tableId + ' input[name=checkthis]').prop('checked', false);
                        }
                    }
                }
                //展示区状态(新增)
                function fun2() {
                    $('#' + showId).empty();//清空展示区里面的内容
                    updateData();
                    //如果没有选中任何选择框,则弹出提示
                    if (globalV.length == 0) {
                        alert('请先选择!');
                    } else {
                        //如果选中了一些选择框,则全局变量数据不为空,开始遍历全局变量
                        for (var j = 0; j < globalV.length; j++) {
                            //按照全局变量globalV,给展示区创建元素;(包含了删除按钮)
                            var liElement = '<li>
                                                 <span>'+ globalV[j].id + '</span>
                                                 <p style="display:inline-block;20px;height:20px;background-color:red;border-radius:50%;text-align:center">X</p>
                                             </li>';
                            $('#' + showId).append(liElement);
                        }
                        //给删除按钮添加点击事件
                        bindEvent('#' + showId + ' p', "click", event.removeLi);
                    }
                }
                //展示区状态(删除)
                function fun3() {
                    //var findAndChangeState=$(this).parent('li').children('span').html();
                    //找到这个删除按钮对应的父级标签li下面的span标签的内容;注意:这个是简化;就放在了标签里面,实际情况可能是个属性,获取的这个值对应一个选择框
                    //由这个值来查找对应的选择框,从而改变选择框的状态;
                    //这里是点击了删除按钮,那么与他对应的选择框的选中状态也会被去除
                    var findAndChangeState = $(this).parent('li').children('span').html();
                    //遍历选择框找到与删除按钮对应的选择框,将其状态改为未选中,同时将全选的选择框也改为未选中
                    $('#' + tableId + ' input[name=checkthis]').each(function (i, ele) {
                        if ($(this).parent().children('span').html() == findAndChangeState) {
                            $(this).parent().children('input').prop('checked', false);
                            $('#' + tableId + ' input[name=checkall]').prop('checked', false);
                        }
                    });
                    //改完之后这个删除按钮对应的父级标签
                    $(this).parent('li').remove();
                }
    
                //负责注册事件
                var event = {
                    select: fun1,
                    add: fun2,
                    removeLi: fun3
                };
                var bindEvent = function (selector, type, fun) {
                    $(selector).bind(type, fun);
                };
                //对各个选择框绑定事件
                bindEvent('#' + tableId + ' input[type=checkbox]', "click", event.select);
                //点击添加按钮的事件
                bindEvent('#' + addClickId, "click", event.add);
            };
    
            return {
                globalV: globalV,
                yourChose: yourChose
            }
        })()
        checkBox.yourChose('ul1', 'add', 'ul2');
    </script>
    
    </html>
    组织后的版本

      更改后的版本里的代码其实都是原来的代码,但组织后的效果是:事件统一绑定(bindEvent),画面统一更新(fun1、fun2、fun3),数据统一设定(updateData)。

      区分的很清楚,哪儿出错找哪儿,几乎不会交叉。而且比较容易拓展,像事件可以继续bindEvent绑定,画面更新的函数可以相应与fun1、fun2、fun3并列添加,数据的额外处理可以添加到updateData里。

      这仅仅是代码组织上的优化,其实代码本身也有很多可以改进的地方,像全选的判定、选择区联动删除等都有更好的思路和代码实现。

    5.梦中初醒

      渐渐发现,其实这里面已经有mvc的影子了,各司其职,分工明确,事件绑定那部分就算是一个弱controller,绑定事件,分发事件响应函数;更新画面状态部分相当于view了,更新画面;updateData更新数据部分更新的就是modle;

    6.醍醐灌顶

      这个组织基本够用了,但它并不是真正的MVC,也不是最优组织,需要你,一语道破天机,希望有人能醍醐灌顶....

    顺便看看万金油的MVC模型:

     就是看不懂,是不,哈哈。

  • 相关阅读:
    两人合作
    JUnit单元测试
    结对编程-——游戏五子棋
    使用Junit等工具进行单元测试
    软件工程
    两人项目---打飞机的游戏
    使用Junit等工具进行单元测试
    软件工程
    使用Junit等工具进行单元测试
    软件工程
  • 原文地址:https://www.cnblogs.com/codingHeart/p/6704444.html
Copyright © 2011-2022 走看看