zoukankan      html  css  js  c++  java
  • 前端模块化总结(commonJs,AMD,CMD,ES6 Module)

      前端模块化已经不是一个新技术了,但是在项目中还是碰到一些不太明白各种引入,导出模块的方法的使用和区别的小伙伴。所以还是想总结一下形成文档,来龙去脉搞清楚了,用起来自然不会混淆。

    --------------------------------------------------------------

      补充:在基于webpack的前端工程里,代码里使用了import做模块导入,module.exports 做导出,这个写法也是可以运行的,因为webpack的模块化遵循commonJs规范,而es6的import语句会被转换成__webpack_require__();

    --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

    • 关于commonJs

      对于javaScript语言来说,在ES6标准之前,它是没有模块化的概念的,commonJs规范的提出,也不是来解决JS模块化的问题。commonJs规范提出的本 意,是用来补充ES缺失的规范,希望JS可以能够在任何地方运行,并具备开发大型应用的基础能力,而不是单单作为web脚本来使用。其中,nodeJs的模块系统,就遵循了commonJs规范。

    加载方式:同步加载,根据模块在代码中出现的先后顺序加载;也就是说,只有加载完成,才能执行后面的操作。这样就会对浏览器造成阻塞。而服务器端的文件相当于在本地存储,不存在请求网络等问题。故commonJs规范被认为不适合浏览器环境而适合服务器端;

    执行特点:第一次加载时执行,并缓存执行结果,输出的是一个值的拷贝,即一旦输出一个值,模块内部的变化就影响不到这个值,自然也没有动态更新;

    关键字:

         require:加载外部模块,并返回模块的exports对象;该对象为一个值的拷贝。

         module.exports:暴露模块方法和属性;

       exports:exports其实是指向于module.exports的一个变量,相当于在模块的开始,定义了 var exports = module.exports; 故使用exports时需要注意不要给该变量重新赋值;

      以遵循该规范的nodeJs模块化为例

    require('module'); // 通过模块名加载,需要注意的使,在配置文件中要配置该模块的具体路径;
    require('path'); // 通过路径加载
    // 使用exports添加属性的方式暴露 exports.xxx = xxx; // 暴露xxx属性
    // 或者使用给module.exports赋值的方法 module.exports = yyy; // require时得到的就是运行后yyy值的拷贝

     exports和module.exports 不管谁被重新赋值,他们的关联关系都会断掉。模块最终return出去的是module.exports;

    exports.a = 2;
    module.exports.b = 3;
    
    console.log(module.exports); //{ a: 2, b: 3 }
    console.log(exports); // { a: 2, b: 3 }
    console.log(exports === module.exports); // true
    

     给module.exports重新赋值,exports中新添加的a属性不会返回

    exports.a = 2;
    module.exports = {"b": 3}; // moudle.exports 被重新指向新的内存,它与exports的关联关系断开
    console.log(module.exports);  // { b: 3 }
    console.log(exports);  // { a: 2 }
    console.log(exports === module.exports);  // false
    

     给exports重新赋值,exports与module.exports断开关联

    exports = 2;   // exports重新赋值,切断了与module.exports的联系
    module.exports.b= 3;
    
    console.log(module.exports); // { b: 3 }
    console.log(exports); // 2
    console.log(exports === module.exports); // fasle
    • 关于AMD  

       Asynchronous Module Definition,异步模块加载;异步的加载方式,更适用于浏览器。请求发出后,继续执行其他脚本,依赖于请求结果的代码,放到一个回调函数里;AMD规范的代表则是require.js;

      特点:依赖前置,所有依赖都在模块定义时声明。不管是在define中声明的依赖,还是在callback中通过require加载的依赖,都会先加载完再去执行callback;故它属于运行时加载。这个特性就导致了在模块定义时被声明的依赖,虽然没有被用到,也做了加载执行的操作;更多requireJs的实现细节,可以参考这篇文章

      以require.js为例:define方法用来定义模块,require方法用来加载模块,config方法用来做配置

    config配置

    require.config({
        baseUrl:'js/',
        paths:{  //  需要通过模块名来加载的模块,都定义到这里,支持网络资源,本地资源路径在baseUrl下
            'jquery':'http://xxx.xxxx.com/jquery.js',
            'index':'index/index.js',
        },
        shim:{
            'aaa':{  // 不符合标准的文件,可以在shim中来定义
                deps:['./a','./b'],
                init:function(){
                    return {
                              // 这里定义非标准文件的返回
                    }
                }
            }
        }
    });        
    

      模块定义:define([id,deps,] callback);

    // define 的模块id是唯一的,为避免麻烦,一般可省略,require.js会自动生成一个唯一标识
    define(['jquery','index','./utils'], function($,index,utils){
       // dosth...
         return { // 这里定义模块向外暴露的方法和属性,没有可以省略;
            'aaa': 1,
            'bbb': 1
         } 
    });
    

      define 也可以定义一组键值对并返回,这种用法常用于动态的config配置;

    define({
        'aaa': 1,
        'bbb': 2,
        'ccc': 3
    });
    

      模块载入:require(deps[,callback]);

    require(["moduleName","path","url"], function (module) {
        // dosth;    
    });

    如果要在define中使用require,那需要加入require的依赖,简写也可以省略

    define(function(require, exports, module ) {  // 这种定义模块的方式可以兼容commonJs规范,但实质上还是被转换为requireJs的规范来实现;
    var a = require('a'), // 通过这种方式可以实现按需加载
    b = require('b');
    // 模块需要暴露的方法也可以通过 exports向外暴露
    exports.eee = 123;
    });
    // 等价于:
    define(['require'], function(require){
    var a = require('a'),
    b = require('b');
    })
    • 关于CMD

         Common Module Definition 通用模块定义,CMD规范的概念是随着sea.js的推出产生的。同时,sea.js也借鉴了很多require.js的东西。它与 CommonJS 和 Node.js 的 Modules 规范保持了很大的兼容性。通过 CMD 规范书写的模块,可以很容易在 Node.js 中运行;

          特点:同AMD一样,CMD也属于运行时加载。但是CMD规范中,依赖模块可以通过require.async在需要的地方引入并执行(懒加载)。这个特性使它可以实现按需加载。

       以sea.js为例:通过define定义模块,通过require加载模块,通过exports或return向外提供API;

    模块定义:define(id?, deps?, factory);factory可以是函数,也可以是对象或者字符串

    // define 函数的标准使用方法。但是官方强烈推荐不传入 id 和 deps,模块加载器会自动获取这两个参数,id默认为模块所在文件的访问路径,
    // deps数组模块加载器会从factory.toString() 中解析。同时,function的第一个参数,必须是require,这是seajs的使用规则;
    define('hello', ['jquery'], function(require, exports, module) {   //dosth...
      // 向外暴露模块的API有三种方式:   exports.aaa = sth; // seajs中exports也是module.exports的引用;
      module.exports = {}
      return {}
    });
    // factory 为对象,相当于定义一个json数据模块,加载该模块时得到的就是这组json数据;这个用法跟require是一致的 define({ "foo": "bar" });
    // factory为字符串时,相当于定义一个字符串模板 define('I am a template. My name is {{name}}.');  

    模块引用:require(id);  require 在seajs中可以看作是语法关键字,不可重新赋值,不可引用;id为要引用模块的唯一标识,且必须是字符串直接量

    define(function(require, exports, module) {
      // 同步加载模块,通过这种方式加载的文件,会在静态分析阶段就被下载好;
        var a = require('./a');
        a.doSomething();
      // 异步加载一个模块,通过这种方式加载的文件,在用的时候才会下载;
        require.async('./b', function(b) {
            b.doSomething();
        });
      
      // 异步加载多个模块
        require.async(['./c', './d'], function(c, d) {
           // do something
        });
      // 条件加载模块;PS:如果这里依然使用require来加载模块,那模块加载器会把两个模块都下载下来
        if (todayIsWeekend){
             require.async("play");
        }else{
             require.async("work");
        }
    });
    

      无论是AMD还是CMD,都是module2.0 的一个分支,正所谓条条大路通罗马,也没有哪个解决方案就明显优于哪个。作为一个前端开发,内心永远向往大一统。ES6的模块化,在目前看来,已经算是前端模块化的大一统了。

    • 关于ES6 Module

      就目前考虑浏览器的兼容性来说,ES6的模块化,还是需要进行兼容性转化的,当然,这个在前端自动化构建的大潮中已经不需要再被提起了。

      特点:与AMD和CMD不同的是,它的设计理念是尽量的静态化,在编译阶段就能确定模块的依赖关系,输入输出等,即编译时加载(静态加载);与commonJs提供的是值的拷贝不同得是,export向外提供的是一个只读的动态引用,故通过import加载的模块,是不会被缓存的。这一特点也说明ES6是支持动态更新的。

           关键字:通过import引用其他模块,通过export对外提供接口。

    export default anything;  // 模块的默认输出,一个模块只能有一个默认输出; 
    // 本质上,export default就是输出一个叫做default的变量或方法,然后系统允许你为它取任意名字。所以default后面不能跟变量声明语句。
    import anyName  from  'path';
    
    export var a = 1;  //这里要说明一点,export是对外的接口,所以它必须与模块内部的变量建立一一对应关系;
    export function  foo(){};
    //  也可以写成下面的形式:
    var  a = 1;
    function foo(){};
    export {a, foo}
    
    // 然而下面的这两种写法都是错误的,因为它没有提供对外的接口
    export 1; 
    
    var a = 1;
    export a;
    
    // 在import中,就要指出要加载的方法和属性的具体名字
    import {a, foo} from 'path';
    // 通过 as关键字,可以给对外输出的方法重新命名。
    function v1() { ... } 
    function v2() { ... } 
    export { 
      v1 as streamV1, 
      v2 as streamV2, 
      v2 as streamLatestVersion // 同一个方法可以输出多次
    };
    // import 中也支持as关键字
    import { streamV1 as newName } from 'path';
    // import也可以整体加载
    import * as newName from 'path';
    
    // import也可以用来加载并执行一个js文件,并且没有任何输入。
    import 'path';   
    import 'path';  // 即使多次加载,该文件也只执行一次,
    
    // 对于同一个模块中的多次加载,import也只执行一次,因为import语句是单例的(singleton)
    import {foo} from 'path';
    import {bar} from 'path';
    //相当于
    import {foo, bar} from 'path';
    

     commonJs规范和es6模块化规范对循环加载的处理

      本来想再总结下这个知识点,但是看了大神阮一峰的总结文章,感觉已经很清晰明了了,这里贴出链接:http://www.ruanyifeng.com/blog/2015/11/circular-dependency.html

      总之一句话,commonJs中遇到循环引用,是执行了多少返回多少,因为它是值的拷贝。ES6里是值的引用,方法或者属性真正被使用的时候才去取。所以如果你的打包构建是基于webpack,那要尽量避免循环引用,或者保证循环引用的变量和方法是已经运行过的。

     
  • 相关阅读:
    数据库系统概论习题集 第八章 数据库并发控制
    数据库系统概论习题集 第七章 数据库恢复技术
    数据库系统概论习题集 第六章 数据库设计
    数据库系统概论习题集 第五章 关系数据理论
    数据库系统概论习题集 第四章 关系系统及其优化
    数据库系统概论习题集 第三章 SQL语言
    【转载】【笔记】vue-router之路由传递参数
    【Dos】复制指定文件夹下所有文件到另外指定文件夹下
    【转载】Vue.js 安装及其环境搭建
    【转载】解决:'webpack-dev-server' 不是内部或外部命令,也不是可运行的程序 或批处理文件。
  • 原文地址:https://www.cnblogs.com/solaZhan/p/12454669.html
Copyright © 2011-2022 走看看