基于ES6的模塊化開發(fā)(附件:基于Angular-cli的WorkTile效果)


在沒有框架的約束下,我們開發(fā)項(xiàng)目可能都是基于過程的,想到哪里就添加一個(gè)函數(shù)。這在項(xiàng)目開發(fā)的初期可能是很快的,特別是對于前端項(xiàng)目。但在后期修改需求的時(shí)候就發(fā)現(xiàn) 項(xiàng)目文件存在 功能不明確,職責(zé)混亂的情況,假如有Vue.js 或者Angular.js 等框架約束,這種情況會(huì)相對好些。本文記錄下基于ES6 實(shí)踐模塊化開發(fā)的過程,本文所用到的代碼在github項(xiàng)目上,歡迎各位大神指點(diǎn)。

Angular.js 架構(gòu)圖

這些MVC框架基本都著眼于以數(shù)據(jù)模型為中心,打造數(shù)據(jù)驅(qū)動(dòng)的模塊化前端應(yīng)用??蚣芸赡軐映霾桓F,學(xué)也學(xué)不完,但基本的思想是不變的。以 Angular.js 的架構(gòu) 為例,Component(組件) 和 Template(HTML模板) 分別代表了Web App的數(shù)據(jù) 和 視圖兩大部分,數(shù)據(jù)的存儲、更新過程都是在我們定義的組件中,組件中包含的數(shù)據(jù)模型更新都會(huì)通過數(shù)據(jù)綁定引起視圖的更新。

而用戶對用戶界面的操作,可以通過事先定義的各種Directive(指令),反饋到數(shù)據(jù)模型中。比如 ngModel 這樣的指令就可用于綁定視圖對數(shù)據(jù)模型的更新?;贏ngular這樣的框架開發(fā)過程中,基本就是不斷寫組件,寫模板,寫指令的過程。那么扯遠(yuǎn)了,Angular 的模塊化系統(tǒng)和ES6 的還是有很大差異的。說了半天,框架畢竟是別人團(tuán)隊(duì)開發(fā)的,你大可去用,從ES6 這樣的本源出發(fā)去學(xué)習(xí)實(shí)踐更加以不變應(yīng)萬變。

ES6 簡單入門

簡單地說,ES6 新的特性可分為以下幾點(diǎn):

  • Classes and Modules (這回主要談一談模塊)
  • New methods for strings and Arrays, Promises, Maps, Sets
  • Completely new features: Generators, Proxies

定義Class

定義一個(gè)類
class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  toString() {
    return '(' + this.x + ',' + this.y + ')';
  }
}

// es6 的class 等同于 function,就是構(gòu)造函數(shù)
Point.prototype.constructor === Point // true

var point = new Point(2, 3);
point.toString()

point.hasOwnProperty('x') //true
point.hasOwnProperty('toString') // false
// toString 方法是原型對象Point 的屬性, 而不是屬于point 實(shí)例的屬性,是通過查找原型鏈得來的。

es6 私有屬性和方法定義

私有方法可以通過將 function 定義在class 作用域之外

// 例如 想要給Point 類一個(gè)私有方法
class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
    this.type = type;
  }
  
  print () {
     toString.call(this);
  }
}

function toString( point ) {
   return point.x + "," + point.y;
}
var type = 'Point';

class 靜態(tài)方法

上面提及的類 都需要實(shí)例化后才能使用,那靜態(tài)方法可以使我們無需實(shí)例化就 通過類直接調(diào)用

 class Format {
    static transform(jsonStr) {
      return JSON.parse(jsonStr);
    }
 }

  // 靜態(tài)屬性 extension
 Format.extension = {
   geojson: ".json"
 }

 class GeoJSON extends Format {
 }

 GeoJSON.transform("{'name': 'hello'}"   // 直接調(diào)用 靜態(tài)方法

ES6轉(zhuǎn)碼打包


由于大部分瀏覽器還沒有支持ES6 模塊,所以可采用Babel 轉(zhuǎn)
來把我們的代碼轉(zhuǎn)化為es5.

然后用 Webpack 打包所有js文件 為一個(gè)bundle ,也可以采用SystemJS的依賴管理方案,實(shí)現(xiàn)瀏覽器端的模塊加載

由于之前在Angular的 實(shí)踐過程中采用的是 SystemJS,所以這次把兩種方法都討論演示下。需要說明的是,這兩種瀏覽器端加載es6模塊的方法都需要Babel的支持,根據(jù)具體情況可選用 Webpack 或SystemJS。

模塊編寫過程

比如我們現(xiàn)在有 drone 和 bullet 兩個(gè)類,drone 可以通過fire() 方法創(chuàng)建bullet 實(shí)例,并且通過一個(gè)全局的 RenderBullet 方法計(jì)算bullet 軌跡。

就這么簡單的需求,因?yàn)閐rone 和bullet 在我們的游戲應(yīng)用中是 基礎(chǔ)類,所以單獨(dú)寫成模塊。常數(shù)變量至于const.js 中。

// drone.js
import Const from './const';
import Bullet from './bullet';

/**
 * Drone class with control method.
 */
export default class Drone {
    constructor(opts) {
        this.id;
        this.speed = opts.speed ? opts.speed: 0.01;
        this.direction = opts.direction ? opts.direction: 0;
        this.name = opts.name ? opts.name: this.randomName();
        this.life = Const.DroneParam.LIFE;
        this.bullets = [];
        this.firing = false;
        this.point = {
            type: 'Point',
            coordinates: [121, 31]
        }
        this.bulletNum = 2;
    }
    // .... 省略飛控代碼。。  
  
    fire () {
        // if not firing, start firing for specific duration.
        if (!this.firing) {
            for (let i = 0; i < this.bulletNum; i ++) {
                this.bullets.push(new Bullet(this));
            }
            this.firing = true;
            setTimeout(() => this.firing = false, Cost.DroneParam.FIRINGTIME);
        }
    }
}

下面簡單看下**bullet.js **的結(jié)構(gòu):

/**
 * Bullet based on Drone instance
 */
export default class Bullet {
    // opts should contain the Drone's direction and geometry
    constructor(opts) {
        this.id;
        this.direciton = opts.direction ? opts.direction: 0;
        this.spoint = {
            type: 'Point',
            coordinates: [0, 0]
        };
        // DeepCopy the drone coords to bullet.
        this.spoint.coordinates[0] = opts.point.coordinates[0];
        this.spoint.coordinates[1] = opts.point.coordinates[1];
    }
}

常量模塊,包含靜態(tài)屬性,無需實(shí)例化直接調(diào)用:

export default class Const {
}

// Static Props outside of class definition
Const.DroneParam = {
    MAXSPEED: 3.999,
    FIRINGTIME: 800,
    LIFE: 10,
    // Firing range.. 0.2 rad in LngLat
    RANGE: 0.2 
};

至此,這就完成了幾個(gè)基礎(chǔ)模塊的編寫,注意: 現(xiàn)在drone.js, bullet.js const.js 這幾個(gè)模塊都在項(xiàng)目的src文件夾下,基于Babel 和 Webpack 轉(zhuǎn)碼打包需要如下過程:

Babel 和Webpack 安裝配置

  • 首先npm 安裝Babel 和 Webpack 庫:

npm install babel-cli babel-core babel-loader webpack babel-preset-latest --save-dev

  • 第二,配置 .babelrc 。在項(xiàng)目根目錄下創(chuàng)建 .babelrc,前面有一個(gè)點(diǎn)啊,別說沒玩過linux。。配置文件都這熊樣,內(nèi)容跟官網(wǎng)一樣。
{ "presets": ["latest"] }
  • 第三,配置 webpack.config.js如下.
module.exports = {
    entry: {
        index: [
            "./src/app.js"
            ]
    },
    output: {
        path: "./dist/",
        filename: "bundle.js",
        // app.js 中導(dǎo)出的模塊都在Alex 這個(gè)Root 命名空間下
        library: 'Alex',
        libraryTarget: 'umd',
    },
    module: {
        loaders: [
        {
            // 用babel 作為 js loader,打包前轉(zhuǎn)碼為es5,沒有中間文件
            test: /\.js$/,
            exclude: /node_modules/,
            loader: 'babel'
        }]
    }
};

說明一下,entry.index 指向的 ./src/app.js 是應(yīng)用的入口文件,也就是說,drone, bullet 等等模塊是寫好了,但是還需要一個(gè)Root 模塊來導(dǎo)出所有模塊(API模式)或者啟動(dòng)應(yīng)用(APP模式)。 當(dāng)然上述兩個(gè)模式是我胡謅的,但是經(jīng)過實(shí)踐確實(shí)證明這兩種模式對應(yīng)模塊化的不同需求。

  • 假如你的 業(yè)務(wù)邏輯代碼 都需要 采用es6 來模塊化編寫(往往是大型應(yīng)用),那么你的app.js 應(yīng)該包含業(yè)務(wù)代碼(APP模式)
  • 假如你的 模塊只是作為 API 供外部代碼調(diào)用,比如 f3earth 這樣的采用es6 編寫的 API,那么你的app.js 應(yīng)該只包含模塊導(dǎo)出的過程(API模式)

比如我的app.js 長這樣:

import Drone from './drone';
// 引入自行封裝的Canvas,渲染游戲場景
import Canvas from './chart/canvas';

export {
        Drone,
        Canvas
} 

這里將所有子模塊再次導(dǎo)出為一個(gè)根模塊,對應(yīng)webpack.config.js 中配置的名為 Alex 的根模塊。在業(yè)務(wù)代碼中通過 Alex.Drone, Alex.Canvas 來調(diào)用不同的類。
至此,就完成了打包前的工作,在根目錄下 cmd中 通過webpack命令開始打包。完成之后,在 dist 目錄下產(chǎn)生 bundle.js,那么這個(gè)文件包含了我們剛才所編寫的所有模塊,可供業(yè)務(wù)代碼調(diào)用。

如果想詳細(xì)了解 Babel,可以直接參考其官網(wǎng)栗子,各種babel 的用法(npm script,或者在webpack中作為loader)
如果想了解更多關(guān)于webpack,可以參考我看過比較簡明易懂的 webpack 入門 這篇文章

寫在最后

根據(jù)上面的過程,我基本編寫了一個(gè)架子,有了幾個(gè)基礎(chǔ)類,但是功能還很弱,而且基于Canvas 的渲染類還在開發(fā)。你看看,這都是些造輪子的工作,但是難免有些人揍喜歡造輪子。。蘇美爾人造出輪子后還是有人在不斷通過造輪子學(xué)習(xí)。

最后我把項(xiàng)目代碼放到了github上,歡迎想了解 ES6 模塊化以及 Webpack 打包以及SystemJS 的同學(xué)去圍觀,clone 下來改裝下可以打造自己的飛機(jī)大戰(zhàn)啊哈哈! 另外也掛出我放在云服務(wù)器上的基于Angular-cli的WorkTile Demo,比較簡陋,歡迎圍觀。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容