模块化开发及AMD、CMD、Require.js、sea.js、common.js、ES6的对比

两盒软妹~` 提交于 2020-10-27 12:21:51

一,模块化开发:

  1. 定义:

    所谓的模块化开发就是封装细节,提供使用接口,彼此之间互不影响,每个模块都是实现某一特定的功能。模块化开发的基础就是函数

  2. 使用函数封装:

    function func1(){
        //...
    }
    function func2(){
        //...
    }
    

    注释 :

    上面的函数func1 ()和func2 (),组成一个模块。使用的时候,直接调用就行了。这种做法的缺点很明显:影响了全局变量,无法保证不与其他模块发生变量名冲突,而且模块成员之间看不出直接关系。

  3. 立即执行函数的写法:

    使用"立即执行函数"(Immediately-Invoked FunctionExpression,IIFE),可以达到不暴露私有成员的目的。这个也是闭包处理的一种方式

    var obj= (function(){
        var _age= 0;
        var func1= function(){
          //...
        };
        var func2= function(){
          //...
        };
        return {
          m1 : func1,
          m2 : func2
        };
    })();
    

    使用上面的写法,外部代码无法读取内部的age变量。

    console.info(obj.age);//undefined
    
  4. 放大模式:

    如果一个模块很大,必须分成几个部分,或者一个模块需要继承另一个模块,这时就有必要采用"放大模式"(augmentation)。在原有的基础上扩展更多的方法。

    var obj =(function (mod){
        mod.func3 = function () {
          //...
        };
        return mod;//方便方法连续调用
    })(obj);
    

    上面的代码为obj模块添加了一个新方法func3 (),然后返回新的obj模块,方便方法连续调用。如何防止obj为null或undefined的情况? 可以采用宽放大模式。

5.宽放大模式(Loose augmentation):

​ 在浏览器环境中,模块的各个部分通常都是从网上获取的,有时无法知道哪个部分会先加载。如果采用上面的写法,第一个执行的部 分有可能加载一个不存在的空对象,这时就要采用"宽放大模式"。

var obj =( function (mod){
    //...
    return mod;
  })(window.obj|| {});//确保对象不为空

6.输入全局变量:

​ 独立性是模块的重要特点,模块内部最好不与程序的其他部分直接交互。为了在模块内部调用全局变量,必须显式地将其他变量输入模块。

(function(window, undefined ) {
……
})(window);

这是jQuery框架的源码,将window对象作为参数传入,这样做除了保证模块的独立性,还使得模块之间的依赖关系变得明显。

目前,通行的JavaScript模块规范共有两种:CommonJS和AMD。

二,AMD、CMD、Require.js、sea.js、common.js、ES6的对比:

他们都是用于在模块化定义中使用的,AMD、CMD、CommonJs是ES5中提供的模块化编程的方案,import/export是ES6中定义新增的

  1. AMD-异步模块定义:

    AMD是RequireJS在推广过程中对模块定义的规范化产出,它是一个概念,RequireJS是对这个概念的实现,就好比JavaScript语言是对ECMAScript规范的实现。AMD是一个组织,RequireJS是在这个组织下自定义的一套脚本语言

    define(['package/lib'], function(lib){ 
      function foo(){
        lib.log('hello');
        return {
            foo: foo
        };
    });
    

    RequireJS:是一个AMD框架,可以异步加载JS文件,按照模块加载方法,通过define()函数定义,第一个参数是一个数组,里面定义一些需要依赖的包,第二个参数是一个回调函数,通过变量来引用模块里面的方法,最后通过return来输出。

    :RequireJS是一个依赖前置、异步定义的AMD框架(在参数里面引入js文件),在定义的同时如果需要用到别的模块,在最前面定义好即在参数数组里面进行引入,在回调里面加载

  2. CMD-同步模块定义:

    CMD是SeaJS在推广过程中对模块定义的规范化产出,是一个同步模块定义,是SeaJS的一个标准,SeaJS是CMD概念的一个实现,SeaJS是淘宝团队提供的一个模块开发的js框架

    define(function(require,exports,module){
      //通过require 引入依赖(依赖就近原则)
      var $ = require('jquery');
      var Spinning = require('./spinning');
    }
    

    通过define()定义,没有依赖前置,通过require加载jQuery插件,CMD是依赖就近,在什么地方使用到插件就在什么地方require该插件,即用即返,这是一个同步的概念

  3. Require.js规范:

    RequireJS是一个工具库,主要用于客户端的模块管理。它可以让客户端的代码分成一个个模块,实现异步或动态加载,从而提高代码的性能和可维护性。它的模块管理遵守AMD规范(Asynchronous Module Definition)。

    RequireJS的基本思想是,通过define方法,将代码定义为模块;通过require方法,实现代码的模块加载。

    首先,将require.js嵌入网页,然后就能在网页中进行模块化编程了。

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

    上面代码的data-main属性不可省略,用于指定主代码所在的脚本文件,在上例中为scripts子目录下的main.js文件。用户自定义的代码就放在这个main.js文件中。

    define方法:定义模块

    define方法用于定义模块,RequireJS要求每个模块放在一个单独的文件里。

    按照是否依赖其他模块,可以分成两种情况讨论。第一种情况是定义独立模块,即所定义的模块不依赖其他模块;第二种情况是定义非独立模块,即所定义的模块依赖于其他模块。

    (1)独立模块

    如果被定义的模块是一个独立模块,不需要依赖任何其他模块,可以直接用define方法生成。

    define({
        method1: function() {},
        method2: function() {},
    });
    

    上面代码生成了一个拥有method1、method2两个方法的模块。

    另一种等价的写法是,把对象写成一个函数,该函数的返回值就是输出的模块。

    define(function () {
        return {
            method1: function() {},
            method2: function() {},
        };
    });
    

    后一种写法的自由度更高一点,可以在函数体内写一些模块初始化代码。

    值得指出的是,define定义的模块可以返回任何值,不限于对象。

    (2)非独立模块

    如果被定义的模块需要依赖其他模块,则define方法必须采用下面的格式。

    define(['module1', 'module2'], function(m1, m2) {
       ...
    });
    

    define方法的第一个参数是一个数组,它的成员是当前模块所依赖的模块。比如,[‘module1’, ‘module2’]表示我们定义的这个新模块依赖于module1模块和module2模块,只有先加载这两个模块,新模块才能正常运行。一般情况下,module1模块和module2模块指的是,当前目录下的module1.js文件和module2.js文件,等同于写成[’./module1’, ‘./module2’]。

    define方法的第二个参数是一个函数,当前面数组的所有成员加载成功后,它将被调用。它的参数与数组的成员一一对应,比如function(m1, m2)就表示,这个函数的第一个参数m1对应module1模块,第二个参数m2对应module2模块。这个函数必须返回一个对象,供其他模块调用。

    define(['module1', 'module2'], function(m1, m2) {
    
        return {
            method: function() {
                m1.methodA();
                m2.methodB();
            }
        };
    
    });
    

    上面代码表示新模块返回一个对象,该对象的method方法就是外部调用的接口,menthod方法内部调用了m1模块的methodA方法和m2模块的methodB方法。

    需要注意的是,回调函数必须返回一个对象,这个对象就是你定义的模块。

    如果依赖的模块很多,参数与模块一一对应的写法非常麻烦。

    define(
        [       'dep1', 'dep2', 'dep3', 'dep4', 'dep5', 'dep6', 'dep7', 'dep8'],
        function(dep1,   dep2,   dep3,   dep4,   dep5,   dep6,   dep7,   dep8){
            ...
        }
    );
    

    为了避免像上面代码那样繁琐的写法,RequireJS提供一种更简单的写法。

    define(
        function (require) {
            var dep1 = require('dep1'),
                dep2 = require('dep2'),
                dep3 = require('dep3'),
                dep4 = require('dep4'),
                dep5 = require('dep5'),
                dep6 = require('dep6'),
                dep7 = require('dep7'),
                dep8 = require('dep8');
    
                ...
        }
    
    });
    

    下面是一个define实际运用的例子。

    define(['math', 'graph'], 
        function ( math, graph ) {
            return {
                plot: function(x, y){
                    return graph.drawPie(math.randomGrid(x,y));
                }
            }
        };
    );
    

    上面代码定义的模块依赖math和graph两个库,然后返回一个具有plot接口的对象。

    另一个实际的例子是,通过判断浏览器是否为IE,而选择加载zepto或jQuery。

    define(('__proto__' in {} ? ['zepto'] : ['jquery']), function($) {
        return $;
    });
    

    上面代码定义了一个中间模块,该模块先判断浏览器是否支持proto属性(除了IE,其他浏览器都支持),如果返回true,就加载zepto库,否则加载jQuery库。

    require方法:调用模块

    require方法用于调用模块。它的参数与define方法类似。

    require(['foo', 'bar'], function ( foo, bar ) {
            foo.doSomething();
    });
    

    上面方法表示加载foo和bar两个模块,当这两个模块都加载成功后,执行一个回调函数。该回调函数就用来完成具体的任务。

    require方法的第一个参数,是一个表示依赖关系的数组。这个数组可以写得很灵活,请看下面的例子。

    require( [ window.JSON ? undefined : 'util/json2' ], function ( JSON ) {
      JSON = JSON || window.JSON;
    
      console.log( JSON.parse( '{ "JSON" : "HERE" }' ) );
    });
    

    上面代码加载JSON模块时,首先判断浏览器是否原生支持JSON对象。如果是的,则将undefined传入回调函数,否则加载util目录下的json2模块。

    require方法也可以用在define方法内部。

    define(function (require) {
       var otherModule = require('otherModule');
    });
    

    下面的例子显示了如何动态加载模块。

    define(function ( require ) {
        var isReady = false, foobar;
     
        require(['foo', 'bar'], function (foo, bar) {
            isReady = true;
            foobar = foo() + bar();
        });
     
        return {
            isReady: isReady,
            foobar: foobar
        };
    });
    

    上面代码所定义的模块,内部加载了foo和bar两个模块,在没有加载完成前,isReady属性值为false,加载完成后就变成了true。因此,可以根据isReady属性的值,决定下一步的动作。

    下面的例子是模块的输出结果是一个promise对象。

    define(['lib/Deferred'], function( Deferred ){
        var defer = new Deferred(); 
        require(['lib/templates/?index.html','lib/data/?stats'],
            function( template, data ){
                defer.resolve({ template: template, data:data });
            }
        );
        return defer.promise();
    });
    

    上面代码的define方法返回一个promise对象,可以在该对象的then方法,指定下一步的动作。

    如果服务器端采用JSONP模式,则可以直接在require中调用,方法是指定JSONP的callback参数为define。

    require( [ 
        "http://someapi.com/foo?callback=define"
    ], function (data) {
        console.log(data);
    });
    

    require方法允许添加第三个参数,即错误处理的回调函数。

    require(
        [ "backbone" ], 
        function ( Backbone ) {
            return Backbone.View.extend({ /* ... */ });
        }, 
        function (err) {
            // ...
        }
    );
    

    require方法的第三个参数,即处理错误的回调函数,接受一个error对象作为参数。

    require对象还允许指定一个全局性的Error事件的监听函数。所有没有被上面的方法捕获的错误,都会被触发这个监听函数。

    requirejs.onError = function (err) {
        // ...
    };
    
  4. sea.js规范:

    • 在 Sea.js 中,所有 JavaScript 模块都遵循 CMD(Common Module Definition) 模块定义规范。该规范明确了模块的基本书写格式和基本交互规则。

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

    • 使用seajs的步骤:

      HTML里引入seajs

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

      入口:seajs.use("./main");

    • 定义模块define:

      function有三个参数:require参数用来引入别的模块,exports和module用来导出模块公共接口。

      define(**function**(require, exports, module) {
        *//添加jquery依赖**
      \*  **var** $ = require(**'jquery'**);
        **function** *Test*(container){
          **this**.**container** = $(container);
        }
        *//提供模块的外部接口**
      \*  module.exports = *Test*;
        *Test*.**prototype**.changeColor= **function** () {
          **var** v = **this**.**container**;
          v.css(**'color'**,**'red'**);
        }
      });
      
  5. common.js规范:

    CommonJS规范是通过module.exports定义的,在前端浏览器里面并不支持module.exports,通过node.js后端使用的。Nodejs端是使用CommonJS规范的,前端浏览器一般使用AMD、CMD、ES6等定义模块化开发的

    exports.area = function (r) {
      return Math.PI * r * r;
    };
    
    exports.circumference = function (r) {
      return 2 * Math.PI * r;
    };
    

    输出方式有2种:

    • 默认输出—module.exports
    • 带有名字的输出—exports.func
  6. ES6特性模块化:

    AMD,CMD,CommoJs都是ES5里面的规范,下面的export/import是ES6里面的规范。通过export/import输入输出 例:

    export default {
        props:['name'],
        data () {
            return{ }
        },
        methods:{
            increment(){
                this.$emit('incre');
                import('./../until')
            },
            decrement(){
                this.$emit('decre');
            }
        }
    }
    
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!