在沒有框架的約束下,我們開發(fā)項目可能都是基于過程的,想到哪里就添加一個函數(shù)窥岩。這在項目開發(fā)的初期可能是很快的,特別是對于前端項目宰缤。但在后期修改需求的時候就發(fā)現(xiàn) 項目文件存在 功能不明確颂翼,職責混亂的情況,假如有Vue.js 或者Angular.js 等框架約束慨灭,這種情況會相對好些朦乏。本文記錄下基于ES6 實踐模塊化開發(fā)的過程,本文所用到的代碼在github項目上氧骤,歡迎各位大神指點呻疹。
這些MVC框架基本都著眼于以數(shù)據(jù)模型為中心,打造數(shù)據(jù)驅(qū)動的模塊化前端應用筹陵」舸福框架可能層出不窮,學也學不完朦佩,但基本的思想是不變的并思。以 Angular.js 的架構 為例,Component(組件) 和 Template(HTML模板) 分別代表了Web App的數(shù)據(jù) 和 視圖兩大部分语稠,數(shù)據(jù)的存儲宋彼、更新過程都是在我們定義的組件中弄砍,組件中包含的數(shù)據(jù)模型更新都會通過數(shù)據(jù)綁定引起視圖的更新。
而用戶對用戶界面的操作输涕,可以通過事先定義的各種Directive(指令)音婶,反饋到數(shù)據(jù)模型中。比如 ngModel 這樣的指令就可用于綁定視圖對數(shù)據(jù)模型的更新莱坎∫率剑基于Angular這樣的框架開發(fā)過程中,基本就是不斷寫組件檐什,寫模板瞳收,寫指令的過程。那么扯遠了厢汹,Angular 的模塊化系統(tǒng)和ES6 的還是有很大差異的。說了半天谐宙,框架畢竟是別人團隊開發(fā)的烫葬,你大可去用,從ES6 這樣的本源出發(fā)去學習實踐更加以不變應萬變凡蜻。
ES6 簡單入門
簡單地說搭综,ES6 新的特性可分為以下幾點:
- Classes and Modules (這回主要談一談模塊)
- New methods for strings and Arrays, Promises, Maps, Sets
- Completely new features: Generators, Proxies
定義Class
定義一個類
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ',' + this.y + ')';
}
}
// es6 的class 等同于 function,就是構造函數(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 實例的屬性划栓,是通過查找原型鏈得來的兑巾。
es6 私有屬性和方法定義
私有方法可以通過將 function 定義在class 作用域之外
// 例如 想要給Point 類一個私有方法
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)方法
上面提及的類 都需要實例化后才能使用,那靜態(tài)方法可以使我們無需實例化就 通過類直接調(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轉碼打包
由于大部分瀏覽器還沒有支持ES6 模塊忠荞,所以可采用Babel 轉
碼 來把我們的代碼轉化為es5.
然后用 Webpack 打包所有js文件 為一個bundle 蒋歌,也可以采用SystemJS的依賴管理方案,實現(xiàn)瀏覽器端的模塊加載委煤。
由于之前在Angular的 實踐過程中采用的是 SystemJS堂油,所以這次把兩種方法都討論演示下。需要說明的是碧绞,這兩種瀏覽器端加載es6模塊的方法都需要Babel的支持府框,根據(jù)具體情況可選用 Webpack 或SystemJS。
模塊編寫過程
比如我們現(xiàn)在有 drone 和 bullet 兩個類讥邻,drone 可以通過fire() 方法創(chuàng)建bullet 實例迫靖,并且通過一個全局的 RenderBullet 方法計算bullet 軌跡。
就這么簡單的需求兴使,因為drone 和bullet 在我們的游戲應用中是 基礎類系宜,所以單獨寫成模塊。常數(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 **的結構:
/**
* 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)屬性欢策,無需實例化直接調(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
};
至此吆寨,這就完成了幾個基礎模塊的編寫,注意: 現(xiàn)在drone.js, bullet.js const.js 這幾個模塊都在項目的src文件夾下踩寇,基于Babel 和 Webpack 轉碼打包需要如下過程:
Babel 和Webpack 安裝配置
- 首先npm 安裝Babel 和 Webpack 庫:
npm install babel-cli babel-core babel-loader webpack babel-preset-latest --save-dev
- 第二啄清,配置 .babelrc 。在項目根目錄下創(chuàng)建 .babelrc俺孙,前面有一個點啊辣卒,別說沒玩過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 中導出的模塊都在Alex 這個Root 命名空間下
library: 'Alex',
libraryTarget: 'umd',
},
module: {
loaders: [
{
// 用babel 作為 js loader啡莉,打包前轉碼為es5,沒有中間文件
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel'
}]
}
};
說明一下旨剥,entry.index 指向的 ./src/app.js 是應用的入口文件咧欣,也就是說,drone轨帜, bullet 等等模塊是寫好了魄咕,但是還需要一個Root 模塊來導出所有模塊(API模式)或者啟動應用(APP模式)。 當然上述兩個模式是我胡謅的蚌父,但是經(jīng)過實踐確實證明這兩種模式對應模塊化的不同需求哮兰。
- 假如你的 業(yè)務邏輯代碼 都需要 采用es6 來模塊化編寫(往往是大型應用),那么你的app.js 應該包含業(yè)務代碼(APP模式)
- 假如你的 模塊只是作為 API 供外部代碼調(diào)用苟弛,比如 f3earth 這樣的采用es6 編寫的 API,那么你的app.js 應該只包含模塊導出的過程(API模式)
比如我的app.js 長這樣:
import Drone from './drone';
// 引入自行封裝的Canvas奠蹬,渲染游戲場景
import Canvas from './chart/canvas';
export {
Drone,
Canvas
}
這里將所有子模塊再次導出為一個根模塊,對應webpack.config.js 中配置的名為 Alex 的根模塊嗡午。在業(yè)務代碼中通過 Alex.Drone, Alex.Canvas 來調(diào)用不同的類囤躁。
至此,就完成了打包前的工作荔睹,在根目錄下 cmd中 通過webpack命令開始打包狸演。完成之后,在 dist 目錄下產(chǎn)生 bundle.js僻他,那么這個文件包含了我們剛才所編寫的所有模塊宵距,可供業(yè)務代碼調(diào)用。
如果想詳細了解 Babel吨拗,可以直接參考其官網(wǎng)栗子满哪,各種babel 的用法(npm script婿斥,或者在webpack中作為loader)
如果想了解更多關于webpack,可以參考我看過比較簡明易懂的 webpack 入門 這篇文章
寫在最后
根據(jù)上面的過程哨鸭,我基本編寫了一個架子民宿,有了幾個基礎類,但是功能還很弱像鸡,而且基于Canvas 的渲染類還在開發(fā)活鹰。你看看,這都是些造輪子的工作只估,但是難免有些人揍喜歡造輪子志群。。蘇美爾人造出輪子后還是有人在不斷通過造輪子學習蛔钙。
最后我把項目代碼放到了github上锌云,歡迎想了解 ES6 模塊化以及 Webpack 打包以及SystemJS 的同學去圍觀,clone 下來改裝下可以打造自己的飛機大戰(zhàn)啊哈哈吁脱! 另外也掛出我放在云服務器上的基于Angular-cli的WorkTile Demo桑涎,比較簡陋,歡迎圍觀豫喧。