在上篇文章中為大家介紹了JavaScript實作跑馬燈抽獎活動實例程式碼解析與最佳化(一),既然是要寫插件。那麼叫做「插件」的東西肯定是具有的某些特徵能夠滿足我們平時開發的需求或是提高我們的開發效率。那麼叫做插件的東西應該要具有哪些基本特徵呢?讓我們來總結一下:
1.JavaScript 外掛程式一些基本特徵:
配置一定要簡單
在插件中定義的變數不污染全域變數;
同一段程式碼可以在不同的地方重複使用;
使用者可以自訂自己功能參數;
具有銷毀變數和參數的功能;
如果按照以上的幾個特徵來寫插件的話,我們可以總結出一個基本的程式碼結構,我們一個一個的來看:
1.外掛設定要盡可能的簡單
html中配置容器節點
//这里的node-type="reward-area" 是标识我们插件的容器节点 <div class="re-area" node-type="reward-area" >
DOM載入完成以後初始化外掛
$(function() { //这里的 test 是代表容器的 class window.LightRotate.init($('[node-type=reward-area]')); });
2.外掛程式中定義的變數不污染全域變數
JavaScript 具有區塊級作用域的識別碼就是function了。那我們怎麼宣告我們的變數才可以使它不污染全域變數呢?
這裡我們需要用到的一個 JavaScript 函數的自執行的知識點。程式碼如下:
(function(){ // do something })();
3.在不同的地方重複使用功能代碼
這就要用到我們物件導向的知識點,把我們的功能程式碼抽象化成對象,在我們需要使用的時候,實例化對象就可以了。那我們接著第二部的程式碼繼續寫,
// (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,如下:
<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.「順序」取得旋轉燈程式碼的最佳化
因為周圍的燈我是使用絕對定位來做的,所以我需要“順序”的獲取他們的列表,然後操作。
先取得 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()。减小了执行队列的复杂度。
关于JavaScript实现跑马灯抽奖活动实例代码解析与优化(二)的相关知识就给大家介绍到这里,希望本文所述对大家有所帮助。