既然是要編寫外掛。那麼叫做“外掛”的東西肯定是具有的某些特徵能夠滿足我們平時開發的需求或者是提高我們的開發效率。那麼叫做外掛的東西應該具有哪些基本特徵呢?讓我們來總結一下:
1.JavaScript 外掛一些基本特徵:
- 配置一定要簡單
- 外掛中定義的變數不汙染全域性變數;
- 同一段程式碼可以在不同的地方複用;
- 使用者可以自定義自己功能引數;
- 具有銷燬變數和引數的功能;
如果按照以上的幾個特徵來寫外掛的話,我們可以總結出一個基本的程式碼結構,我們一個一個的來看:
1.外掛配置要儘可能的簡單
html中配置容器節點
1 2 |
//這裡的node-type="reward-area" 是標識我們外掛的容器節點 <div class="re-area" node-type="reward-area" > |
DOM載入完成以後初始化外掛
1 2 3 4 |
$(function() { //這裡的 test 是代表容器的 class window.LightRotate.init($('[node-type=reward-area]')); }); |
2.外掛中定義的變數不汙染全域性變數
JavaScript 具有塊級作用域的識別符號就是function
了。那我們怎麼宣告我們的變數才可以使它不汙染全域性變數呢?
這裡我們需要用到的一個 JavaScript
函式的自執行的知識點。程式碼如下:
1 2 3 |
(function(){ // do something })(); |
3.在不同的地方複用功能程式碼
這就要用到我們物件導向的知識點,把我們的功能程式碼抽象成物件,在我們需要使用的時候,例項化物件就可以了。那我們接著第二部的程式碼繼續寫,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// (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.使用者可以自定義功能引數
首先我們應該有預設的引數設定,比如下面這樣
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
// (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
中,如下:
1 2 3 4 5 6 |
<div class="re-area" node-type="reward-area" data-setting='{ "liAutoPlay":false, "roLiSpeed":100, "roPrSpeed":200, "liDirection":true, "randomPrize":false}'> |
這樣使用者只需要在 html的節點中就可以配置當前容器執行的引數。這樣的好處還可以使同一頁面上的不同容器,可以單獨的配置引數,減少耦合。
那麼在 js 中我們該怎麼獲取這些引數呢?在上面的程式碼中,已經有了功能物件函式。那麼我們想擴充套件物件方法來獲取使用者的自定義引數,怎麼辦呢?我們一般使用prototype
的東西來擴充套件我們已有物件的方法,程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
// (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.銷燬變數和引數的功能;
最後一個就是我們的外掛應該具有銷燬自身變數和引數的功能。我們該怎麼寫呢?還是在上面的程式碼基礎上繼續擴充套件功能物件的可呼叫方法,程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
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節點。
1 2 3 4 5 |
//獲取外圍的燈,可以看到我這裡使用的選擇器多了一個 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 元素列表。
我的第一版是這樣做的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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次迴圈的使用
1 2 3 4 5 6 7 8 9 10 11 |
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 執行佇列中去。
程式碼是下面這樣子的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
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 中“閉包”符合我當前的使用場景,就想著用閉包優化一下,優化後程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
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 上反饋給我。
打賞支援我寫出更多好文章,謝謝!
打賞作者
打賞支援我寫出更多好文章,謝謝!
任選一種支付方式