• js模块化起源和现有的规范与框架

    普通类
    • 支持
    • 批判
    • 提问
    • 解释
    • 补充
    • 删除
    • 模块化

    背景

    Web应用的快速发展、后端逻辑的前端化,在客观上要求多人协作编写js代码,这样就带来了以下问题:

    • 全局变量命名混乱冲突导致系统错误

    • 代码集中少量文件导致开发效率降低

    • 容易出现功能重复开发

    类似于上述js的加载是早期的网页制作中最常见的,可这样做的缺点是加载时的文件越多网页等待响应的时间越长;而且如果js之间存在依赖关系,则必须严格按照依赖性最强的放置在最后的规则。依赖关系会严重影响代码的编写和维护的效率。。。

     

    模块化框架对于按需加载、模块划分、代码复用、自动文档、单元测试、团队合作等都有很大好处。

    模块就是实现特定功能的一组方法,理想情况下,开发者只需要实现核心的业务逻辑,其他都可以加载别人已经写好的模块。

    表格 模块化

    实现方式

    优缺点


     

    优点:代码简单啊

     

    缺点:全是全局变量,容易冲突


     

    优点:属性封装在对象中,看起来是减少了上述问题

     

    缺点:会暴露所有模块成员,内部状态可以被外部改写(module1._count = 5


     

    立即执行的函数写法

    优点:隐藏了私有变量,只返回需要暴露的内容

    这是模块化基本的实现思路


     

    放大模式,如果一个模块很大,必须分成几个部分,或者一个模块需要继承另一个模块时使用

     

    优点:对基本方式的加强,可对特定的对象进行增强,如对左边的module1添加一个方法


     

    宽放大模式(与"放大模式"相比,区别在于"立即执行函数"的参数可以是空对象)

     

    优点:避免“放大模式”中因加载顺序导致的未定义的错误

     

    • 规范

    CommonJS

    CommonJS是服务器端模块的规范,根据此规范,一个单独的文件就是一个模块。加载模块使用一个全局性的require方法,该方法读取一个文件并执行,最后返回文件内部的exports对象。(将需要实现的方法都添加到export对象上

    1. 比如有如下一个简单的模块文件(lcell.js

    console.log("进入lcell.js 加载模块文件");

    exports.message = "学习元平台(http://lcell.bnu.edu.cn";

    exports.say = function () {

    console.log(message);

    }

     

    1. 在需要使用此模块的地方使用require加载

    var lcell = require('./lcell'); //.js后缀可省略

     

    1. 此时的变量lcell就对应模块中的exports对象,因此可以通过这个对象调用模块中提供的各种方法

    lcell.say();

     

    有时,不需要exports返回一个对象,只需要它返回一个函数。这时,就要写成module.exports

    module.exports = function () {

    console.log("返回一个方法");

    }

     

    CommonJS存在的问题在于如果目标模块很大,则需要全部等待加载完之后代码才可以继续执行,这样表面看起来是假死状态。

    node.js采用了CommonJS的规范 (可自行了解。推荐一本书《node.js开发指南》)

    AMD (Asynchronous Module Definition)

    上述CommonJS规范加载模块是同步的,只有加载完成,才能执行后面的操作。由于Node.js主要用于服务器编程,模块文件一般都已经存在于本地硬盘,所以加载起来比较快,不用考虑非同步加载的方式,所以CommonJS规范比较适用。但是如果是浏览器环境,要从服务器端加载模块,这时就必须采用非同步模式,因此浏览器端一般采用AMD规范。

    AMD规范则是异步加载模块,需指定回调函数。也采用require()语句加载模块,不同的是它是异步的,等各模块加载完成后调用callback回调函数。

    使用方法:require([module], callback);

     

    AMD规范使用define方法定义模块

    define(id?, dependencies?, factory);

    • id: 模块标识,可以省略

    • dependencies: 所依赖的模块,可以省略

    • factory: 模块的实现,或者一个JavaScript对象

    表格 AMD模块定义

    类别

    例子

    定义无依赖的模块

    define(function() {

        return {

            mix: function(source, target) {

            }

        };

    });

    定义有依赖的模块

    define(['base'], function(base) {

        return {

            show: function() {

                // todo with module base

            }

        }

    });

    定义数据对象模块

    define({

        users: [],

        members: []

    });

    具名模块

    define('index', ['data','base'], function(data, base) {

        // 代码

    });

    包装模块(允许输出的模块兼容CommonJS规范

    define(function(require, exports, module) {

        var base = require('base');

        exports.show = function() {

            // 代码

        }

    });

    总之,AMD 规范是JavaScript开发的一次重要尝试,用简单、优雅的方式统一了JavaScript的模块定义和加载机制。

    1实现AMD的库有RequireJS curl Dojo bdLoadJSLocalnet Nodules

    CMD

    AMD 规范相比,CMD 规范尽量保持简单,并与 CommonJS Node.js Modules 规范保持了很大的兼容性。经常使用的 API 只有 define, require, require.async, exports, module.exports 这五个。

    CMD 规范中,一个模块就是一个文件。

    define(factory);

     

    表格 CMD模块定义

    类别

    例子

    定义对象或字符串模块

    define({ "foo": "bar" });

    define('I am a template. My name is {{name}}.');

    构造模块

    define(function(require, exports, module) {

    // 模块代码

    });

    //require 是一个方法,接受 模块标识 作为唯一参数,用来获取其他模块提供的接口。

    //exports 是一个对象,用来向外提供模块接口

    //module 是一个对象,上面存储了与当前模块相关联的一些属性和方法

     

    定义有依赖的模块

    //define(id?, deps?, factory)

    define('hello', ['jquery'], function(require, exports, module) {

    // 模块代码

    });

    一个空对象

    //可用来判定当前页面是否有 CMD 模块加载器

    if (typeof define === "function" && define.cmd) {

    // Sea.js CMD 模块加载器存在

    }

    模块内部异步加载模块

    define(function(require, exports, module) {

    // 异步加载多个模块,在加载完成时,执行回调

    require.async(['./c', './d'], function(c, d) {

    c.doSomething();

    d.doSomething();

    });

    });

    exports向外提供接口

    define(function(require, exports) {

    // 对外提供 foo 属性

    exports.foo = 'bar';

    // 对外提供 doSomething 方法

    exports.doSomething = function() {};

    });

    //或直接通过 return 直接提供接口

    define(function(require) {

    return {

    foo: 'bar',

    doSomething: function() {}

    };

    });

    module.exports

    define(function(require, exports, module) {

    //下面的module不可省略

    module.exports = {

    foo: 'bar',

    doSomething: function() {}

    };

    });

    因为exportsmodule.exports的引用,如果省略module.而直接给exports重新赋值不会改变module.exports的值,赋值是无效的

     

    • 模块加载器

    ESL2

    ESL是一个浏览器端、符合AMD的标准加载器,适合用于现代Web浏览器端应用的入口与模块管理。

    在目标网页引入esl.js。然后对其进行配置(通过require.config( Object ),可以对esl进行配置

    <script src="js/esl.js"></script>

     

    <script type="text/javascript">

    require.config( {

    baseUrl: './src', //指定模块的根路径

    paths:{

    ‘echarts’:'js/echarts', //为特殊模块指定查找路径

    'echarts/chart/line': 'js/echarts',

    lib: 'http://lib.com/libpath' // 引用外部模块可以用paths配置

    },

    packages: [ //功能集合的抽象,通过packages配置可以引入一个符合CommonJS规范的包。

    {

    name: 'er',

    location: '../dep/er/3.0.2'

    },

    {

    name: 'echarts',

    location: '../dep/echarts/1.1.0',

    main: 'echarts'

    }

    ],

    //以下是map配置项,允许用户对依赖的模块进行映射。

    'some/newmodule': { //some/newmodule模块中,require('foo')实际引用到的是foo2模块

    'foo': 'foo2',

    'foo/bar': 'foo1.2/bar3'

    },

    //config配置可以为模块传递需要动态变更的参数,比如功能开关。模块可以通过module.config()获得其自身配置

    config: {

    sidebar: {

    displayMode: 'autohide'

    }

    },

    waitSeconds: 5, //指定等待的时间,如果有模块未成功加载或初始化,将抛出异常错误信息

    urlArgs: 'v=2.0.0' //在模块路径后添加参数字符串,更新此配置将导致相关模块缓存全部失效

    } );

     

    //require函数加载模块并在回调函数中使用

    require(

    [

    'echarts',

    'echarts/chart/bar',

    'echarts/chart/line'

    ],

    function(ec) {

    var myChart = ec.init(document.getElementById('main'));

    //其他操作……

    }

    );

    </script>

     

    Require.js

    在目标网页载入require.jsdata-main属性指定网页程序的主模块,其中main.js省略后缀名。

    <script src="js/require.js" data-main="js/main"></script>

     

    主模块是整个js文件的入口代码。类似于C语言的main()函数。main.js使用AMD规范定义的的require()函数,定义主模块依赖的其他模块并设定回调函数。

    require.config({

    baseUrl: "js/lib", //指定模块的根路径

        paths: { //为各模块指定查找路径

          "jquery": "lib/jquery.min",

          "underscore": "lib/underscore.min",

          "backbone": "lib/backbone.min"

        }

     });

     

    require(['jquery', 'underscore', 'backbone'], function (\\$, _, Backbone){

        //操作各个模块

    });

    require.js加载的模块需要遵守AMD规范(见第4页),必须采用特定的define()函数来定义。

    描述

    定义方法

    加载方式

    无依赖模块

    // math.js

    define(function (){

      var add = function (x,y){

        return x+y;

      };

      return {

        add: add

      };

    });

    require(['math'], function (math){

    alert(math.add(1,1));

    });

    依赖于其他模块

    //foo.js

    define(['myLib'], function(myLib){

    function foo(){

      myLib.doSomething();

    }

    return {

      foo : foo

    };

    });

     

    require(['foo'], function foo){

    foo.foo();

    });

     

    //当加载foo模块的时候会先加载myLib模块

    非标准的js文件

     

    require.config({

      shim: {

    'underscore':{

          exports: '_'

        },

        'backbone': {

        deps: ['underscore', 'jquery'],

         exports: 'Backbone'

        }

      }

    });

    // shim属性专门用来配置不兼容的模块。每个模块要定义exports值(输出的变量名),表明这个模块外部调用时的名称;deps数组,表明该模块的依赖性。


    sea.js

    SeaJS是一个遵循CMD规范的JavaScript模块加载框架,作者是淘宝前端工程师玉伯。

    在目标页面引入sea.js

    <script src="js/sea.js" ></script>

    然后对seajs 进行简单配置:

    seajs.config({

    base: "../sea-modules/", // 设置路径

    alias: { // 为模块设置别名

    "jquery": "jquery/jquery/1.10.1/jquery.js"

    }

    })

    // 加载入口模块。seajs.use用来在页面中加载一个或多个模块,此处加载主模块

    seajs.use("../static/hello/src/main");

     

    表格 seajs最常用的接口

    最常用的接口

    例子

    seajs.config

    seajs.config({

    base: "../sea-modules/", // 设置路径

    alias: { // 为模块设置别名

    "jquery": "jquery/jquery/1.10.1/jquery.js"

    }

    })

    seajs.use

    // 支持加载多个模块,包含依赖关系,在加载完成时,执行回调

    seajs.use(['./a', './b'], function(a, b) {

    a.doSomething();

    b.doSomething();

    });

    define

    // 模块的定义

    define(function(require, exports, module) {

    // 模块代码

    });

    require

    // 获取指定模块的接口,只接受字符串直接量作为参数

    define(function(require) {

    // 获取模块 a 的接口

    var a = require('./a');

    // 调用模块 a 的方法

    a.doSomething();

    });

    require.async

    define(function(require) {

    // 异步加载多个模块,在加载完成时,执行回调

    require.async(['./c', './d'], function(c, d) {

    c.doSomething();

    d.doSomething();

    });

    });

    exports

    // 对外提供接口。exports其实是module.exports的引用

    define(function(require, exports) {

    // 对外提供 foo 属性

    exports.foo = 'bar';

    // 对外提供 doSomething 方法

    exports.doSomething = function() {};

    });

    module.exports

    //完全重写module.exports属性来对外提供接口

    define(function(require, exports, module) {

    // 对外提供接口

    module.exports = {

    name: 'a',

    doSomething: function() {};

    };

    });

    • 比较

    表格 模块加载器比较比较

    比较内容

    RequireJS

    SeaJS

    ESL

    遵循规范

    AMD

    CMD

    AMD

    策略

    提前准备依赖

    按需执行依赖

    提前准备依赖

    API

    一个当多个用

    严格区分,职责单一

     

    定位

    文件和模块加载器,特别为浏览器优化,同时也可运行在 Rhino Node 环境中。

    适用于浏览器端的JavaScript 模块加载器

    仅支持浏览器端

    shim支持

    支持

    支持

    不支持

    文件大小

    81K

    3K

    49K

     

     

    1 CMD模块定义规范https://github.com/seajs/seajs/issues/242

     

    2 https://github.com/ecomfe/esl

     

     

    • 在新页面中查看内容
    • 下载源文件
    • 标签:
    • 文件
    • 定义
    • 39
    • exports
    • 加载
    • function
    • define
    • js
    • 模块化
    • require
    • 模块
    • 规范
  • 加入的知识群:
    学习元评论 (0条)

    评论为空
    聪明如你,不妨在这 发表你的看法与心得 ~



    登录之后可以发表学习元评论
      
暂无内容~~
顶部