ES6零基礎教學_解析彩票項目-學習筆記(三)

彩票項目實戰學習記錄(三)

這裏是主要的業務邏輯,主要是針對模塊化作筆記記錄。html

模塊化

這裏不關注業務邏輯代碼,只關注實現和應用 es6開發
  1. 對於沒有強耦合性的東西進行模塊化
  2. 對於功能模塊進行模塊化
  3. 管理模塊化的模塊

因此分紅了四個模塊+2個特殊模塊:前端

  • app/js/lottery/calculate.js 計算模塊-負責彩票投注數和獎金數運算的
  • app/js/lottery/timer.js 倒計時模塊
  • app/js/lottery/base.js 基礎模塊-跟彩票自己相關的基礎信息的模塊
  • app/js/lottery/interface.js 接口模塊-負責跟彩票中心(至關於後臺)交互的模塊
  • app/js/index.js 入口模塊-特殊模塊1,負責處理整個應用的入口管理
  • app/js/lottery.js 整合模塊-特殊模塊2,負責整合被分散的模塊

計算模塊calculate.js

  • 使用 class 類的寫法,更加直觀的管理代碼。
class Calculate {
    
}
// 使用 es6的語法導出這個 class
export default Calculate
  • 方法computeCount,代碼以下:
/**
 * [computeCount 計算注數]
 * @param  {number} active    [當前選中的號碼的個數]
 * @param  {string} play_name [當前的玩法標識,如R2,即任二]
 * @return {number}           [注數]
 */
computeCount(active, play_name) {
    let count = 0;
    // 使用 es6的 map 結構
    const exist = this.play_list.has(play_name); //判斷玩法列表裏面是否有這樣的玩法,set形式
    // 使用 es6的填充數組功能 fill
    const arr = new Array(active).fill('0'); //生成長度爲active的數組,並填充爲0
    if (exist && play_name.at(0) === 'r') {
        // 調用靜態方法combine
        count = Calculate.combine(arr, play_name.split('')[1]).length;
    }
    return count;
}
  • map 結構數據能夠很方便的判斷是否包含某個元素,這裏一行代碼就獲得告終果
  • 數組填充功能主要是方便填充一個數組來進行計算,
  • 由於 es6裏面支持 class,也支持 static 靜態方法,因此能夠實現調用靜態方法的處理,因爲是靜態方法,因此須要使用class 來調用。
  • 靜態方法combine

這是裏面combine方法,這是一個靜態方法,這是 es6纔有的,須要 static 關鍵字便可。jquery

/**
 * [combine 組合運算 C(m,n)]
 */
static combine(arr, size) { 
    // 省略
    })(arr, size, [])
    return allResult
}

倒計時模塊timer.js

  1. 倒計時模塊也是使用 class 的方式寫的。
  2. 支持2個回調函數傳入,一個是倒計時更新的回調函數,一個是倒計時結束的回調函數。
  3. 這裏注意到回調函數都使用 call 的方式來使用,目的是爲了保證 this 指向保持不變,因此使用 call 而且傳入 self。
  4. 這裏的倒計時主要邏輯在於 setTimeout 部分,經過 setTimeout 不斷調用自身 countdown 函數來實現了倒計時的效果。
  5. 很標準的一個倒計時模塊寫法,能夠參考學習。
class Timer {
    /**
     * 倒計時方法
     * @param  number end    截止時間
     * @param  function update 每次更新時間時的回調函數
     * @param  function handle 倒計時結束時的回調函數
     * @return
     */
    countdown(end, update, handle) {
        const now = new Date().getTime();
        const self = this;
        if (now - end > 0) {
            handle.call(self);
        } else {
            // 剩餘時間
            let last_time = end - now;
            // 常量,用來處理毫秒轉天,時,分,秒
            const px_d = 1000 * 60 * 60 * 24;
            const px_h = 1000 * 60 * 60;
            const px_m = 1000 * 60;
            const px_s = 1000;
            // 剩餘時間轉換爲天,時,分,秒
            let d = Math.floor(last_time / px_d);
            // 須要減去天的毫秒數
            let h = Math.floor((last_time - d * px_d) / px_h);
            // 須要減去天和小時的毫秒數
            let m = Math.floor((last_time - d * px_d - h * px_h) / px_m);
            // 須要減去天和小時和分鐘的毫秒數
            let s = Math.floor((last_time - d * px_d - h * px_h - m * px_m) / px_s);
            let r = [];
            if (d > 0) {
                r.push(`<em>${d}</em>天`);
            }
            // 判斷數組長度主要是爲了防止數據錯亂,例如只有時,沒有分,秒的狀況
            if (r.length || (h > 0)) {
                r.push(`<em>${h}</em>時`);
            }
            if (r.length || m > 0) {
                r.push(`<em>${m}</em>分`);
            }
            if (r.length || s > 0) {
                r.push(`<em>${s}</em>秒`);
            }
            // self.last_time = r.join('');
            // 執行更新回調函數,使用的是計算以後的倒計時時間
            update.call(self, r.join(''));
            // 間隔每秒執行一次,從新執行倒計時程序
            setTimeout(function () {
                self.countdown(end, update, handle);
            }, 1000);
        }
    }
}

export default Timer

基礎模塊base.js

  • 導入 jquery,由於須要操做 dom 數據。
import $ from 'jquery';
  • 方法initNumber

這裏的初始化號碼由於須要補0,因此要使用 es7才提供的 padStart,須要藉助babel-polyfill來實現,由於他的方便易用性,因此會被大量地被你們使用。git

/**
   * [initNumber 初始化號碼]
   * @return {[type]} [description]
   */
  initNumber(){
    for(let i=1;i<12;i++){
      this.number.add((''+i).padStart(2,'0'))
    }
  }
  • 方法setOmit
omit 的數據是在整合模塊裏面被定義爲一個 map 結構的數據的,下面有說。

這裏主要關注 map 結構的應用:程序員

  • 清空數據 clear
  • 添加數據 set
  • 獲取數據 get
  • 遍歷數據可使用 for...of 的方式,使用omit 的 entries()獲取到全部值,而後遍歷
/**
   * [setOmit 設置遺漏數據]
   * @param {[type]} omit [description]
   */
  setOmit(omit){
    let self=this;
    // map 結構處理數據
    self.omit.clear();
    for(let [index,item] of omit.entries()){ //omit是個map結構
      self.omit.set(index,item)
    }
    $(self.omit_el).each(function(index,item){
      $(item).text(self.omit.get(index))
    });
  }
  • 方法addCodeItem

這裏須要注意2個地方:es6

  1. es6的字符串模板,代替了以往的+的方式,很是直觀而且簡單。
  2. self.getTotal()直接調用當前實例的方法,這個其實很大程度上,將項目 class 化,而後經過實例這個 class,而後很方便的使用這個實例的全部方法。
/**
   * [addCodeItem 添加單次號碼]
   * @param {[type]} code     [description]
   * @param {[type]} type     [description]
   * @param {[type]} typeName [description]
   * @param {[type]} count    [description]
   */
  addCodeItem(code,type,typeName,count){
    let self=this;
    // es6的字符串模板使用
    const tpl=`
    <li codes="${type}|${code}" bonus="${count*2}" count="${count}">
         <div class="code">
             <b>${typeName}${count>1?'複式':'單式'}</b>
             <b class="em">${code}</b>
             [${count}注,<em class="code-list-money">${count*2}</em>元]
         </div>
     </li>
    `;
    $(self.cart_el).append(tpl);
    self.getTotal(); //獲取總金額
  }

接口模塊interface.js

  • 導入了 jquery 模塊來使用,導入的目的是由於這個接口模塊裏面使用了其餘模塊的函數,例如self.setOmit(res.data);,由於這個函數裏面涉及了 jquery 的相關使用,因此若是在這裏須要使用的話,就要引入 jquerygithub

    • 須要注意 this 的指向,這裏由於是在閉包裏面,this 指向會被改變,因此須要提早保存 this 指向。
  • 使用了 es6的 promise進行異步操做,promise能夠代替 es5的無限回調問題。ajax

    • 這裏 resolve 使用 call 的意思也是保持 this 指向不被改變。
import $ from 'jquery';

class Interface{
 /**
   * 先獲取遺漏數據,而後進行前端顯示,須要promise
   * @param  string issue json數據
   * @return promise
   */
  getOmit(issue){
    // 保存 this 指向
    let self=this;
    // es6的 promise
    return new Promise((resolve,reject)=>{
      $.ajax({
        url:'/get/omit',
        data:{
          issue:issue
        },
        dataType:'json',
        success:function(res){
          self.setOmit(res.data);
          resolve.call(self,res)
        },
        error:function(err){
          reject.call(err);
        }
      })
    });
  }
  // 省略

整合模塊lottery.js

  • 導入模塊json

    • 導入全部須要的模塊,由於這個是整合模塊,因此須要導入全部以前寫的模塊,主要路徑,路徑是從當前模塊文件的路徑開始計算。
    • 這裏是有順序要求的,由於有些語法須要使用babel-polyfill來處理,因此須要先導入它
import 'babel-polyfill';
import Base from './lottery/base.js';
import Timer from './lottery/timer.js';
import Calculate from './lottery/calculate.js';
import Interface from './lottery/interface.js';
import $ from 'jquery';
  • 深度拷貝數組

    • 這裏使用深度拷貝的緣由是由於複製的是對象,而且使用 es6的方式進行深度拷貝。
    • es6裏面Reflect.ownKeys能夠拿到原對象的全部屬性。參考Reflect
    • 構造函數,原型,name 這3個屬性不須要拷貝,因此要排除。
const copyProperties = function (target, source) {
    for (let key of Reflect.ownKeys(source)) {  //拿到源對象上的全部屬性
        if (key !== 'constructor' && key !== 'prototype' && key !== 'name') {  //過濾
            let desc = Object.getOwnPropertyDescriptor(source, key); // 獲取指定對象的自身屬性描述符
            Object.defineProperty(target, key, desc);
        }
    }
}

備註:

關於Object.definePropertyObject.getOwnPropertyDescriptor

  • 前者就是爲了給對象定義或修改屬性的,若是配合後者來使用的話,那麼就會變成直接給目標對象定義一個真實可用的屬性(由於後者能夠獲取源對象的真實屬性)
  • 經過遍歷使用,就至關於可以複製了一個新對象了。

關於深拷貝(深複製)和淺拷貝(淺複製)

  • JS的數據類型能夠分爲兩種:基本數據類型(null,undefined,string,number和boolean)和引用數據類型(Object,Array,function)。
  • 由於對象和數組在賦值的時候都是引用傳遞。賦值的時候只是傳遞一個指針。若是一個引用類型賦值給一個變量,那麼這個變量裝的是這個對象的地址!那麼就會出現修改他的「引用」的值的時候,源值也被改變了。
  • 「淺拷貝」就是複製一份引用,全部引用對象都指向一份數據,而且均可以修改這份數據。
  • 「深拷貝」就是可以實現真正意義上的數組和對象的拷貝。深複製不是簡單的複製引用,而是在堆中從新分配內存,而且把源對象實例的全部屬性都進行新建複製,以保證深複製的對象的引用圖不包含任何原有對象或對象圖上的任何對象,複製後的對象與原來的對象是徹底隔離的

多重繼承 Mixin

多重繼承能夠根據各個不一樣的功能模塊分不一樣程序員獨立開發,最後合併起來,並且功能模塊耦合度比較小,出現BUG也能很快定義到功能模塊,修改其中某一個對其餘沒有影響。

js 設計的時候是定位爲簡單的腳本語言,因此沒有考慮 class 和繼承的問題,直至如今也依然木有考慮繼承的問題,都是 js 的開發者本身開發出來使用的,全部就有了這種相似多重繼承的多重繼承,本質上是將一個對象的屬性拷貝到另外一個對象上面去,其實就是對象的融合。

參考:Mixin、多重繼承與裝飾者模式

  • 使用 es6的 rest 語法...mixins,利用 rest 語法獲取函數的多餘參數簡化了寫法。
  • 使用以前提到的深度拷貝函數進行多重繼承

    • 須要注意的是 js 對象的原型prototype也須要單獨進行拷貝,由於原型鏈沒了,對象就是object,所屬的類沒了,若是有須要這個所屬的類的相關屬性或者方法的話,就沒辦法調用了。
const mix = function (...mixins) {
    class Mix {
    }  //聲明一個空的類
    for (let mixin of mixins) {
        copyProperties(Mix, mixin);  //深度拷貝
        copyProperties(Mix.prototype, mixin.prototype);  //也拷貝原型
    }
    return Mix
}

多重繼承class

將Lottery進行多重繼承,繼承來自Base, Calculate, Interface, Timer的 class。

  • 使用 extends繼承,繼承後須要重寫父類屬性使用super(),但須要注意順序,必須先使用super()
  • 構造函數進行Lottery的一些屬性的初始化。
  • 能夠看到不少使用了 Map和 Set 結構的數據
  • 多重繼承以後,可使用其餘來自於被繼承的 class 的方法和屬性
class Lottery extends mix(Base, Calculate, Interface, Timer) {
    constructor(name = 'syy', cname = '11選5', issue = '**', state = '**') {
        super();
        this.name = name;
        this.cname = cname;
        this.issue = issue;
        this.state = state;
        this.el = '';
        this.omit = new Map();
        this.open_code = new Set();  //開獎號碼
        this.open_code_list = new Set(); //開獎記錄
        this.play_list = new Map();
        this.number = new Set();  //獎號
        this.issue_el = '#curr_issue';
        this.countdown_el = '#countdown'; //倒計時的選擇器
        this.state_el = '.state_el'; //狀態的選擇器
        this.cart_el = '.codelist'; //購物車的選擇器
        this.omit_el = ''; //遺漏
        this.cur_play = 'r5'; //當前的默認玩法
        // 這個方法是在其餘類中已經被實現了,這裏只須要直接調用便可
        this.initPlayList();
        this.initNumber();
        this.updateState(); //更新狀態
        this.initEvent();
    }

方法updateState

這裏經過異步獲取到後臺的數據,而後根據獲得的數據結果進行調用倒計時函數等操做。這是一個比較完整的異步調用函數處理寫法方式。

/**
* [updateState 狀態更新]
* @return {[type]} [description]
*/
updateState() {
   let self = this;
   this.getState().then(function (res) {  // getState()是接口裏的方法
       self.issue = res.issue; //拿到期號
       self.end_time = res.end_time; //拿到截止時間
       self.state = res.state; //拿到狀態
       $(self.issue_el).text(res.issue); //更新期號
       self.countdown(res.end_time, function (time) { //倒計時
           $(self.countdown_el).html(time)
       }, function () { //從新獲取
           setTimeout(function () {
               self.updateState();
               self.getOmit(self.issue).then(function (res) {

               });
               self.getOpenCode(self.issue).then(function (res) {

               })
           }, 500);
       })
   })
}

入口模塊index.js

這個入口就將邏輯分離出來了,若是須要處理多個入口,能夠很方便的在這裏根據不一樣餓實例導入不一樣的模塊,而且統一管理。

import Lottery from './lottery';  //引入彩票的入口文件

// 實例化Lottery實例
const syy=new Lottery();
相關文章
相關標籤/搜索