前端代碼組織優化--小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;width: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;width: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模型:

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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM