前端模块化之CJS、AMD、CMD、UMD、ES6

2,092 阅读5分钟

Javascript 前端模块化。其中就有 CJSAMDCMDUMD 和 ESM

CJS

CJS 是CommonJS的缩写。主要用于后端,在nodejs中,node应用是由模块组成,采用的Commonjs模块规范。

语法

暴露模块 module.exports=valueexports=module.export exports.xxx=value

引入模块 require(xxx)(如果引入为第三方模块,xxx为模块名;如为自定义模块,xxx为模块的文件路径)

const doSomething = require('./doSomething.js');

module.exports = function doSomething(n) { 
    // do something 
}
特点
  • 每一个文件就是一个模块,拥有自己独立的作用域,变量,以及方法等,对其他的模块都不可见。
  • 模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。
  • 模块加载的顺序,按照其在代码中出现的顺序。
  • CJS模范规定,每个模块内部module变量为当前模块,这个变量其实是一个对象,他的exports属性(module.exports)是对外的接口(加载某个模块其实是加载改模块的module.exports属性)
  • CJS加载机制是输入的是输出的值的拷贝,输出一个值,在脚本执行完后才生成,模块内部变化影响不到这个值(理解:require是浅拷贝,也就是说你可以修改对象第二层的属性并影响原数据)
// module.js 
let counts = 1;
function sayHello() { alert(`"hello , ${counts}`) } 
setTimeout(() => { counts += 2 }, 3000); 
module.exports = {counts, sayHello}; 
//index.js 
const { counts, sayHello } = require('./module.js'); 
// 注意此处的代码结果 
const click = () => { console.log('counts: ', counts) 
// 每次点击事件,打印的 counts 始终为 1 
sayHello(); // 1 ==》 3秒钟后 =>> 3 }
... <!-- 此处counts始终是 1 --> 
<p>import {counts}</p> 
<Button type="primary" onClick={click}>require </Button>

AMD

CJS 加载模块是同步的,只有加载完成才执行后续的操作。AMD规范是非同步加载模块,允许制定回调函数。Node.js主要是服务器编程,模块文件一般在本地磁盘,加载比较快,不要考虑同步回调的方法,所以CJS适用,但浏览器环境要从服务端加载模块,就需要非同步模式,因此(在没ES6前)浏览器一般使用AMD规范

语法
  • 规范代表库:require.js

暴露模块:无依赖的模块 define(function(){ return xxx}) 有依赖的模块define(['module1','module2'], function(m1,m2){ return xxx})

引入模块 require(['module1','module2'], function(m1,m2){ //使用m1,m2})

特点
  • AMD依赖定义方法清晰,不污染全局环境,明确依赖关系
  • 可用于浏览器环境,且允许非同步加载,也可根据需要动态加载

CMD

CMD与AMD很类似,不同点在于:AMD推崇依赖前置、提前执行,CMD推崇依赖就近、延迟执行。此规范其实是在sea.js推广过程中产生的。

代码示例
  • 规范代表库:sea.js SeaJS是一个JavaScript模块加载框架,可以实现JavaScript的模块化开发及加载机制。
define(function(require, exports, module) { 
    var a = require('./a'); //在需要时申明
    a.doSomething(); 
    if (false) { 
        var b = require('./b'); 
        b.doSomething();
    } 
}); 
 /** sea.js **/ 
 // 定义模块 math.js 
 define(function(require, exports, module) { 
     var $ = require('jquery.js'); 
     var add = function(a,b){ return a+b; } 
     exports.add = add; 
 }); 
 // 加载模块 
 seajs.use(['math.js'], function(math){ 
 var sum = math.add(1+2); 
 });

UMD

UMD规范只是一种通用的写法,是在AMD和CJS两个流行而不统一的规范情况下,才催生出UMD来统一规范的,UMD前后端均可通用。

  • 规范代表库:sea.js

SeaJS是一个JavaScript模块加载框架,可以实现JavaScript的模块化开发及加载机制。

代码示例

    (function (root, factory) {
        if (typeof define === 'function' && define.amd) {
            // AMD
            define(['jquery', 'underscore'], factory);
        } else if (typeof exports === 'object') {
            // Node, CommonJS之类的
            module.exports = factory(require('jquery'), require('underscore'));
        } else {
            // 浏览器全局变量(root 即 window)
            root.returnExports = factory(root.jQuery, root._);
        }
    }(this, function ($, _) {
        // 属性
        var PI = Math.PI;
        // 方法
        function a() { };                   // 私有方法,因为它没被返回
        function b() { return a() };        // 公共方法,因为被返回了
        function c(x, y) { return x + y };  // 公共方法,因为被返回了
        // 暴露公共方法
        return {
            ip: PI,
            b: b,
            c: c
        }
    }));

ESM

ESM 代表 ES 模块。这是 Javascript 提出的实现一个标准模块系统的方案。

代码示例
import {foo, bar} from './myLib'; 
... 
export default function() { 
    // your Function 
}; 
export const function1() { ...};
export const function2() {...};
特点
  • ES6模块化,是尽量静态化,编译时就能确定模块的依赖关系,以及输入输出变量。CJS和AMD都是运行时确定。比如,CommonJS 模块就是对象,输入时必须查找对象属性。
  • ES6 模块不是对象,而是通过export命令显式指定输出的代码,再通过import命令输入。ES6对外接口是一种静态定义,代码解析就生成
  •  ES6 的静态模块结构,可以进行 Tree Shaking。允许像 Rollup 这样的打包器,删除不必要的代码,减少代码包可以获得更快的加载
  • 代码是在模块作用域之中运行,而不是在全局作用域运行。模块内部的顶层变量,外部不可见。
  • 模块脚本自动采用严格模式,不管有没有声明use strict。模块之中,顶层的this关键字返回undefined,而不是指向window。也就是说,在模块顶层使用this关键字,是无意义的。
  • 模块之中,可以使用import命令加载其他模块(.js后缀不可省略,需要提供绝对 URL 或相对 URL),也可以使用export命令输出对外接口。
  • 同一个模块如果加载多次,将只执行一次。

总结+补充

1.CJS主要用于服务器端,加载模块同步,所以不适合浏览器,因此有了AMD,CMD方案

2.AMD在浏览器异步加载模块,且并行加载多个模块,不过AMD开发成本高阅读代码比较困难,模块定义方式不流

3.CMD,AMD相似,都用于浏览器,CMD依赖就近,延迟执行,可以在使用前才声明;而AMD推崇依赖前置,提前执行,在最前声明的时候,初始化了所有的声明模块

4.ESM简单语法,异步特性和和可摇树性

ESM和CJS区别

  • CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
  • CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
  • CommonJS 模块的require()是同步加载模块,ES6 模块的import命令是异步加载,有一个独立的模块依赖的解析阶段。

第一个差异是因为CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。ES6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。换句话说,ES6 的import有点像 Unix 系统的“符号连接”,原始值变了,import加载的值也会跟着变。因此,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。

第二个差异是因为 CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。

参考

es6.ruanyifeng.com/#docs/modul…

dev.to/iggredible/…