JavaScript 「跑馬燈」抽獎活動代碼解析與優化(二)

既然是要編寫插件。那麼叫作「插件」的東西確定是具備的某些特徵可以知足咱們平時開發的需求或者是提升咱們的開發效率。那麼叫作插件的東西應該具備哪些基本特徵呢?讓咱們來總結一下:javascript

1.JavaScript 插件一些基本特徵:

  1. 配置必定要簡單
  2. 插件中定義的變量不污染全局變量;
  3. 同一段代碼能夠在不一樣的地方複用;
  4. 用戶能夠自定義本身功能參數;
  5. 具備銷燬變量和參數的功能;

若是按照以上的幾個特徵來寫插件的話,咱們能夠總結出一個基本的代碼結構,咱們一個一個的來看:html

1.插件配置要儘量的簡單

html中配置容器節點java

//這裏的node-type="reward-area" 是標識咱們插件的容器節點
<div class="re-area" node-type="reward-area" >

DOM加載完成之後初始化插件node

$(function() {
    //這裏的 test 是表明容器的 class
    window.LightRotate.init($('[node-type=reward-area]'));
});

2.插件中定義的變量不污染全局變量

JavaScript 具備塊級做用域的標識符就是function了。那咱們怎麼聲明咱們的變量纔可使它不污染全局變量呢?
這裏咱們須要用到的一個 JavaScript 函數的自執行的知識點。代碼以下:git

(function(){
    // do something
})();

3.在不一樣的地方複用功能代碼

這就要用到咱們面向對象的知識點,把咱們的功能代碼抽象成對象,在咱們須要使用的時候,實例化對象就能夠了。那咱們接着第二部的代碼繼續寫,github

//
(function($){
    // 建立功能對象
    var LightRotate = function (select) {
        // do something
    };

    LightRotate.init = function (select) {
        var _this = this;
        //根據不一樣的容器實例化不一樣的對象
        select.each(function () {
            new _this($(this));
        });
    };

    window.LightRotate = LightRotate;
})(jQuery);

4.用戶能夠自定義功能參數

首先咱們應該有默認的參數設定,好比下面這樣閉包

//
(function($){
    // 建立功能對象
    var LightRotate = function (select) {
        // 自定義的參數
        this.setting = {
            liAutoPlay: false,  //周圍的燈是否自動旋轉
            roLiSpeed: 100,     //燈旋轉的速度ms
            roPrSpeed: 200,     //獎品旋轉速度ms
            liDirection: true,  //旋轉方向 true  正方向   false  反方向
            randomPrize: false  //空格是否隨機選取
        };
    };

    LightRotate.init = function (select) {
        var _this = this;
        //根據不一樣的容器實例化不一樣的對象
        select.each(function () {
            new _this($(this));
        });
    };

    window.LightRotate = LightRotate;
})(jQuery);

其實這樣寫的話,使用者已經能夠修改咱們的 JavaScript 文件來完成自定義了。可是爲了可以讓咱們的差價足夠的好用,好比說,咱們的使用者一點兒都不懂 js 呢?該怎麼辦?
這樣咱們能夠把這些參數用自定義屬性配置在 html中,以下:dom

<div class="re-area" node-type="reward-area" data-setting='{
    "liAutoPlay":false,
    "roLiSpeed":100,
    "roPrSpeed":200,
    "liDirection":true,
    "randomPrize":false}'>

這樣用戶只須要在 html的節點中就能夠配置當前容器運行的參數。這樣的好處還可使同一頁面上的不一樣容器,能夠單獨的配置參數,減小耦合。函數

那麼在 js 中咱們該怎麼獲取這些參數呢?在上面的代碼中,已經有了功能對象函數。那麼咱們想擴展對象方法來獲取用戶的自定義參數,怎麼辦呢?咱們通常使用prototype的東西來擴展咱們已有對象的方法,代碼以下:優化

//
(function($){
    // 建立功能對象
    var LightRotate = function (select) {
        // 自定義的參數
        this.setting = {
            liAutoPlay: false,  //周圍的燈是否自動旋轉
            roLiSpeed: 100,     //燈旋轉的速度ms
            roPrSpeed: 200,     //獎品旋轉速度ms
            liDirection: true,  //旋轉方向 true  正方向   false  反方向
            randomPrize: false  //空格是否隨機選取
        };
        
        //這裏調用對象的獲取用戶自定義參數的方法,而且將默認參數合併
        $.extend(_this.setting, _this.getSettingUser());
    };

    LightRotate.prototype = {
        //擴展獲取用戶自定義參數的方法
        getSettingUser: function () {
            var userSetting = this.LightArea.attr('data-setting');
            if (userSetting && userSetting !== '') {
                return $.parseJSON(userSetting);
            } else {
                return {};
            }
        }
    };

    LightRotate.init = function (select) {
        var _this = this;
        //根據不一樣的容器實例化不一樣的對象
        select.each(function () {
            new _this($(this));
        });
    };

    window.LightRotate = LightRotate;
})(jQuery);

5.銷燬變量和參數的功能;

最後一個就是咱們的插件應該具備銷燬自身變量和參數的功能。咱們該怎麼寫呢?仍是在上面的代碼基礎上繼續擴展功能對象的可調用方法,代碼以下:

LightRotate.prototype = {
        //擴展獲取用戶自定義參數的方法
        getSettingUser: function () {
            var userSetting = this.LightArea.attr('data-setting');
            if (userSetting && userSetting !== '') {
                return $.parseJSON(userSetting);
            } else {
                return {};
            }
        },
        //銷燬對象參數
        destory: function () {
            $(_this.LightArea).off();
            this.closeAnimation();
            this.rewardTimer = null;
        }
    };

由以上咱們的內容咱們能夠大概瞭解了一個成熟的插件應該具備的基本功能。

2.插件開發和優化示例

恰好這個項目是在春節放假前的一個緊急的項目,當時爲了趕進度就沒有詳細思考本身的代碼結構,這樣野味本身的後續優化提供了機會。

由上一節介紹的定時器的內容能夠知道 JavaScript 是單線程的。因此

若是一段代碼運行效率很低,就會影響後續代碼的執行。因此對於 JavaScript ,代碼優化是必須的。

先來看看咱們的「跑馬燈」插件應該具備哪些功能:

  1. 可以控制燈是否自動播放;
  2. 燈的旋轉方向能夠控制;
  3. 燈的旋轉速度能夠控制;
  4. 獎品的旋轉速度能夠控制;

這裏就不詳細的介紹這些功能點的開發過程,僅僅介紹優化過程。若是有興趣能夠看我文章最後附上的源代碼地址,進行下載閱讀。

1.「順序」獲取旋轉燈代碼的優化

由於周圍的燈我是使用絕對定位來作的,因此我須要「順序」的獲取他們的列表,而後操做。

首先獲取 DOM節點。

//獲取外圍的燈,能夠看到我這裏使用的選擇器多了一個 select,是爲了獲取當前容器下的某些元素,避免有多個容器存在時衝突
this.topLight = $('[node-type=re-top]', select).find('span');
this.rightLight = $('[node-type=re-right]', select).find('span');
this.bottomLight = $('[node-type=re-bottom]', select).find('span');
this.leftLight = $('[node-type=re-left]', select).find('span');

而後就應該「順序」的獲取「燈」節點的 DOM 元素列表。

個人初版是這樣作的:

Zepto(topLight).each(function() {
      lightList.push(this);
});

Zepto(rightLight).each(function() {
      lightList.push(this);
});

for (var j = bottomLight.length - 1; j >= 0; j--) {
     lightList.push(bottomLight[j]);
}

for (var m = leftLight.length - 1; m >= 0; m--) {
       lightList.push(leftLight[m]);
}

由於「下」和「左」方向的燈是須要倒序的,因此我使用了兩個倒序的 for循環,其實當循環出現的時候,咱們都應該思考咱們的代碼是否有可優化的空間。

優化後的代碼是這樣子的,在這裏我減小了4次循環的使用

function () {
    var lightList = [];
    var bottomRever;
    var leftRever;
     bottomRever = Array.from(this.bottomLight).reverse();
     leftRever = Array.from(this.leftLight).reverse();

     lightList = Array.from(this.topLight).concat(Array.from(this.rightLight));
     lightList = lightList.concat(bottomRever);
     lightList = lightList.concat(leftRever);
     }

列表倒序我使用了原生 Array對象的reverse方法。

2.使用「閉包」優化順序循環播放

爲了可以使咱們的「燈」順序的跑起來,初版的思路是:

每個「燈」(注意,這裏是每個,罪過...罪過...)定義一個setTimeout(),執行時間就是數序的加入 js 執行隊列中去。

代碼是下面這樣子的:

var zepto_light = Zepto(lightList);
            var changeTime = 100;
            var lightLength = zepto_light.length;
            var totleTime = changeTime * lightLength;

            function lightOpen() {
                for (var i = 0; i < lightLength; i++) {
                    (function temp(i) {
                        lightTimer = setTimeout(function() {
                            if (stopAnimation === false) {
                                Zepto(zepto_light).removeClass('light_open');
                                Zepto(zepto_light[i]).addClass("light_open");
                            } else {
                                return;
                            }
                        }, changeTime * i);
                    })(i);
                }
            }

這樣子寫的缺點很明顯:若是我有100個「燈」那麼就會在當前的 js 執行隊列中加入100個setTimeout(),再次強調的是我這裏又使用了for循環,在時間複雜度上又增長了。代碼的執行效率又降低了。

後來思考了下,JavaScript 中「閉包」符合我當前的使用場景,就想着用閉包優化一下,優化後代碼以下:

lightRun: function () {
            var _this = this;
            function tempFunc() {
                var lightList = _this.getLightList();
                var lightLength = lightList.length;
                var i = 0;
                return function () {

                    $(lightList, _this.LightArea).removeClass('light_open');
                    $(lightList[i], _this.LightArea).addClass("light_open");
                    i++;

                    //使一輪循環結束後可以繼續下次循環
                    if (i === lightLength) {
                        i = 0;
                    }
                };
            }

            var lightRunFunc = tempFunc();
            lightRunFunc();
            _this.lightInterVal = setInterval(lightRunFunc, _this.setting.roLiSpeed);
        }

由以上的代碼能夠很明顯的發現兩個優勢:第一,就是減小了 for循環的使用,下降了代碼的時間複雜度,第二就是,每次我僅僅在當前代碼執行的隊列中建立一個setInterval()。減少了執行隊列的複雜度。

到這裏關於「跑馬燈」插件的代碼解析詳和優化就已經完了。詳細的代碼和使用文檔請點擊連接。若是有什麼問題能夠隨時在 github 上反饋給我。

這裏寫圖片描述

相關文章
相關標籤/搜索