美文网首页JavaScript学习笔记前端日报让前端飞
基于ES6的模块化开发(附件:基于Angular-cli的Wor

基于ES6的模块化开发(附件:基于Angular-cli的Wor

作者: 四爷在此 | 来源:发表于2016-12-17 17:25 被阅读733次

    在没有框架的约束下,我们开发项目可能都是基于过程的,想到哪里就添加一个函数。这在项目开发的初期可能是很快的,特别是对于前端项目。但在后期修改需求的时候就发现 项目文件存在 功能不明确,职责混乱的情况,假如有Vue.js 或者Angular.js 等框架约束,这种情况会相对好些。本文记录下基于ES6 实践模块化开发的过程,本文所用到的代码在github项目上,欢迎各位大神指点。

    Angular.js 架构图

    这些MVC框架基本都着眼于以数据模型为中心,打造数据驱动的模块化前端应用。框架可能层出不穷,学也学不完,但基本的思想是不变的。以 Angular.js 的架构 为例,Component(组件) 和 Template(HTML模板) 分别代表了Web App的数据 和 视图两大部分,数据的存储、更新过程都是在我们定义的组件中,组件中包含的数据模型更新都会通过数据绑定引起视图的更新。

    而用户对用户界面的操作,可以通过事先定义的各种Directive(指令),反馈到数据模型中。比如 ngModel 这样的指令就可用于绑定视图对数据模型的更新。基于Angular这样的框架开发过程中,基本就是不断写组件,写模板,写指令的过程。那么扯远了,Angular 的模块化系统和ES6 的还是有很大差异的。说了半天,框架毕竟是别人团队开发的,你大可去用,从ES6 这样的本源出发去学习实践更加以不变应万变。

    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,就是构造函数
    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 静态方法

    上面提及的类 都需要实例化后才能使用,那静态方法可以使我们无需实例化就 通过类直接调用

     class Format {
        static transform(jsonStr) {
          return JSON.parse(jsonStr);
        }
     }
    
      // 静态属性 extension
     Format.extension = {
       geojson: ".json"
     }
    
     class GeoJSON extends Format {
     }
    
     GeoJSON.transform("{'name': 'hello'}"   // 直接调用 静态方法
    

    ES6转码打包


    由于大部分浏览器还没有支持ES6 模块,所以可采用Babel 转
    来把我们的代码转化为es5.

    然后用 Webpack 打包所有js文件 为一个bundle ,也可以采用SystemJS的依赖管理方案,实现浏览器端的模块加载

    由于之前在Angular的 实践过程中采用的是 SystemJS,所以这次把两种方法都讨论演示下。需要说明的是,这两种浏览器端加载es6模块的方法都需要Babel的支持,根据具体情况可选用 Webpack 或SystemJS。

    模块编写过程

    比如我们现在有 drone 和 bullet 两个类,drone 可以通过fire() 方法创建bullet 实例,并且通过一个全局的 RenderBullet 方法计算bullet 轨迹。

    就这么简单的需求,因为drone 和bullet 在我们的游戏应用中是 基础类,所以单独写成模块。常数变量至于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];
        }
    }
    

    常量模块,包含静态属性,无需实例化直接调用:

    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 
    };
    

    至此,这就完成了几个基础模块的编写,注意: 现在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 。在项目根目录下创建 .babelrc,前面有一个点啊,别说没玩过linux。。配置文件都这熊样,内容跟官网一样。
    { "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模式)。 当然上述两个模式是我胡诌的,但是经过实践确实证明这两种模式对应模块化的不同需求。

    • 假如你的 业务逻辑代码 都需要 采用es6 来模块化编写(往往是大型应用),那么你的app.js 应该包含业务代码(APP模式)
    • 假如你的 模块只是作为 API 供外部代码调用,比如 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 的根模块。在业务代码中通过 Alex.Drone, Alex.Canvas 来调用不同的类。
    至此,就完成了打包前的工作,在根目录下 cmd中 通过webpack命令开始打包。完成之后,在 dist 目录下产生 bundle.js,那么这个文件包含了我们刚才所编写的所有模块,可供业务代码调用。

    如果想详细了解 Babel,可以直接参考其官网栗子,各种babel 的用法(npm script,或者在webpack中作为loader)
    如果想了解更多关于webpack,可以参考我看过比较简明易懂的 webpack 入门 这篇文章

    写在最后

    根据上面的过程,我基本编写了一个架子,有了几个基础类,但是功能还很弱,而且基于Canvas 的渲染类还在开发。你看看,这都是些造轮子的工作,但是难免有些人揍喜欢造轮子。。苏美尔人造出轮子后还是有人在不断通过造轮子学习。

    最后我把项目代码放到了github上,欢迎想了解 ES6 模块化以及 Webpack 打包以及SystemJS 的同学去围观,clone 下来改装下可以打造自己的飞机大战啊哈哈! 另外也挂出我放在云服务器上的基于Angular-cli的WorkTile Demo,比较简陋,欢迎围观。

    相关文章

      网友评论

      本文标题:基于ES6的模块化开发(附件:基于Angular-cli的Wor

      本文链接:https://www.haomeiwen.com/subject/phjbpttx.html