javascript技巧

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript技巧 > JS五大模块标准

一文带你搞懂JS五大模块标准:CommonJS / AMD / CMD / UMD / ESM

作者:蜡台

本文将带你彻底搞懂CommonJS、AMD、CMD、UMD和ESM五大规范,从背景、核心特性到实战对比,帮你理清模块化发展脉络,掌握ESM和CommonJS在现代项目中的正确用法,告别全局变量污染和依赖管理噩梦

一、模块化出现背景

早期 JavaScript 不存在原生模块化方案,开发中存在三大痛点:

五大规范可分为三类:

二、CommonJS(简称 CJS)

通过exports和module.exports来暴露模块中的内容。通过require来加载模块。

1.诞生背景:2009 年随 Node.js 诞生,是 Node.js 内置默认模块规范,面向服务端本地文件设计。

2.核心特性

3.代码示例

// math.js
const add = (a, b) => a + b;
module.exports = { add };

// index.js
const math = require('./math');
console.log(math.add(1, 2));

示例2:

// 1 通过exports 暴露模块接口:
// study.js
var hello = function () {
   console.log('hello studygd.com.');
}
exports.hello = hello;

//  main.js
const studygd = require('./study');
studygd.hello();

//2、通过module.exports 暴露模块接口:定义math模块

//math.js
module.exports = {
   add: function(left, right) {
       return left + right;
   },
   subtract: function(left, right) {
       return left - right;
   }
}

//使用刚才定义的math模块, 并再定义一个calculator模块
//calculator.js

const math = require('./math.js');
module.exports = {
    add: math.add
}

4.优缺点

5.适用场景

Node.js 后端项目、打包工具输出 dist/cjs 产物。

三、AMD(Asynchronous Module Definition 异步模块定义)

1.诞生背景

为浏览器网络异步加载设计,代表工具:RequireJS,解决浏览器同步加载阻塞页面问题。

2.核心特性

3.代码示例

// 定义模块
define(['jquery', './math'], function ($, math) {
  function render() {
    $('body').text(math.add(1, 3));
  }
  // 导出模块
  return { render };
});

// 入口加载
require(['./main'], function (app) {
  app.render();
});

示例2:

/// 1.通过define方法定义模块base.js

define(function (){
    var control = {};
    return control;
});


/// control.js
define(['jquery', 'jqmd5', 'cookie', 'base'], function (){
    var control = {};
 
    /**
     * 登录状态检测
     */
    control.cookie = function (){
        setTimeout(WK.LC.syncLoginState, 100);
    };
 
    /**
     * 模块调用及配置
     */
    control.template = function (){
        if($('.naver').length > 0) base.naver();
 
        if(CATEGORY == 'login')
        {
            if(MODEL == 'index'){
                // 登录页
                require(['login'], function (Login){
                    Login.form();
                });
            };
 
            if(MODEL == 'register' || MODEL == 'check'){
                // 注册页
                require(['register'], function (Register){
                    Register.form(MODEL);
                });
            };
        };
 
        if(CATEGORY == 'goods')
        {
            // 详情页
            if(MODEL == 'index'){
                require(['detail'], function (Detail){
                    // Detail.form();
                });
            };
        };
    };
 
    return control;
});


// 2. 通过require方法加载模块(异步加载); 注意:参数里面有define声明的模块

require(['control'], function (Control){
    Control.cookie();
    Control.template();
});

4.优缺点

5.适用场景

老旧 RequireJS 前端项目,目前基本不再使用。

四、CMD(Common Module Definition 通用模块定义)

1.诞生背景

国内阿里 SeaJS 推出,改良 AMD 语法,兼顾 CommonJS 书写习惯与浏览器异步加载。

在定义模块方面, CMD和AMD一样通过define函数来定义模块; 两者的主要区别在于对依赖的加载上, CMD中不需要在define的参数中直接声明需要用到的模块

2.核心特性

3.代码示例

define(function (require, exports, module) {
  // 就近按需引入依赖
  const math = require('./math');
  exports.log = function () {
    console.log(math.add(2, 4));
  };
});

示例2:

// 1.通过define方法定义模块calculator.js
define('calculator', function(require, exports) {
    // 通过require方法加载其他模块
    var math = require('math');
    exports.add = function(left, right) { return math.add(left, right) };
    exports.subtract = function(left, right) { return math.subtract(left, right) };
})
//可以看到calculator模块所的依赖的math模块没有在define函数的参数中进行声明, 而是通过require(‘math')来引入的

//2、使用calculator模块

seajs.use(['calculator'], function(calculator) {
    console.log('1 + 1 = ' + calculator.add(1, 1));
    console.log('2 - 2 = ' + calculator.subtract(2, 1));
})

4.AMD vs CMD 核心对比

对比维度AMD(RequireJS)CMD(SeaJS)
依赖书写位置文件顶部统一前置声明代码就近,用到再引入
依赖执行时机全部依赖下载完成统一执行下载完成后,调用 require 时才执行
代表工具RequireJSSeaJS

5.优缺点

6.适用场景

多年前 SeaJS 遗留老项目,新项目禁止使用。

五、UMD(Universal Module Definition 通用模块定义)

1.诞生背景

为第三方类库设计,实现一套代码兼容 CommonJS / AMD / 浏览器全局 script(CMD) 三种环境

amd cmd 通常只能在浏览器中使用, commonjs只能在服务端(Node)**环境下使用, 这样子搞会导致我们基于其中某一种模块规范写的js模块无法在服务端和浏览器端进行复用.

umd解决了这个问题, 它兼容并包, 使得使用此规范写的 js模块既可以在浏览器环境下使用, 也可以在Node(服务端)环境中用

2.核心特性

通过自执行函数自动判断当前运行环境,自适应不同模块化规范导出。

3.标准 UMD 模板

(function (root, factory) {
  // AMD 环境判断
  if (typeof define === 'function' && define.amd) {
    define(['jquery'], factory);
  }
  // CommonJS / Node 环境判断
  else if (typeof module === 'object' && module.exports) {
    module.exports = factory(require('jquery'));
  }
  // 普通浏览器 script 全局挂载
  else {
    root.myLib = factory(root.jQuery);
  }
})(this, function ($) {
  // 类库核心逻辑
  return {
    sayHi: () => console.log('hello umd lib')
  };
});

示例2

(function (root, factory) {
    if (typeof exports === 'object' && typeof module === 'object')
        // commonjs
        module.exports = factory()
    else if (typeof define === 'function' && define.amd)
        // amd、cmd
        define([], factory)
    else if (typeof exports === 'object')
        // commonjs
        exports['math'] = factory()
    else
        // 全局对象, 浏览器中是 window
        root['math'] = factory()
})(this, function() {
    return { add: function(left, right) { return left + right; } }
})

//其实只要你看过jq源码,你就会觉得 上面的这段代码很熟悉,是的,jq源码里面就是采用了umd规范去做兼容,所以jq可以说是umd规范的一种代表
//附:jq源码大体框架

( function( global, factory ) {
 
    "use strict";
 
    if ( typeof module === "object" && typeof module.exports === "object" ) {
 
        // For CommonJS and CommonJS-like environments where a proper `window`
        // is present, execute the factory and get jQuery.
        // For environments that do not have a `window` with a `document`
        // (such as Node.js), expose a factory as module.exports.
        // This accentuates the need for the creation of a real `window`.
        // e.g. var jQuery = require("jquery")(window);
        // See ticket #14549 for more info.
        module.exports = global.document ?
            factory( global, true ) :
            function( w ) {
                if ( !w.document ) {
                    throw new Error( "jQuery requires a window with a document" );
                }
                return factory( w );
            };
    } else {
        factory( global );
    }
 
// Pass this if window is not defined yet
} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
 
    //这里编写jquery主体代码...
 
    // AMD
    if ( typeof define === "function" && define.amd ) {
        define( "jquery", [], function() {
            return jQuery;
        } );
    }
 
    var
        // Map over jQuery in case of overwrite
        _jQuery = window.jQuery,
 
        // Map over the $ in case of overwrite
        _$ = window.$;
 
    jQuery.noConflict = function( deep ) {
        if ( window.$ === jQuery ) {
            window.$ = _$;
        }
 
        if ( deep && window.jQuery === jQuery ) {
            window.jQuery = _jQuery;
        }
 
        return jQuery;
    };
 
    if ( !noGlobal ) {
        window.jQuery = window.$ = jQuery;
    }
 
    return jQuery;
} );


4.优缺点

5.适用场景

第三方开源类库打包产物(lodash、axios 等 dist/umd 文件)。

六、ESM / ES Module(ES6 Module,现代官方标准)

1.诞生背景

ES2015 (ES6) 官方推出的原生模块化标准,浏览器、Node.js 双端原生支持,当前行业唯一主流规范。

使用import导入模块,通过export导出模块

2.核心特性

3.基础语法示例

// math.js 导出
export const add = (a, b) => a + b;
export const sub = (a, b) => a - b;
// 默认导出
export default { add };

// index.js 导入
import { add, sub } from './math.js';
import math from './math.js';
// 命名空间全部导入
import * as MathUtil from './math.js';
// 动态异步按需导入
import('./math.js').then(mod => console.log(mod.add(1, 2)));

4.浏览器使用方式

<!-- 必须声明 type="module" -->
<script type="module" src="main.js"></script>

5.Node.js 开启 ESM 两种方式

1.项目 package.json 添加配置:“type”: “module”;

2.文件后缀命名为 .mjs。

6.ESM 与 CommonJS 关键差异

特性CommonJS(CJS)ESM
加载阶段运行时动态加载编译期静态解析
Tree-Shaking不支持原生支持
导出绑定值拷贝,无法同步更新实时引用绑定
条件导入if/for 中可写 requireimport 仅允许顶层,动态用 import ()
文件识别默认 .js.js 需配置 type:module /.mjs
循环依赖处理缓存快照,易丢失数据实时绑定,循环依赖更稳定

7.优缺点

8.适用场景

Vue/React 现代前端项目、全新 Node 项目、浏览器原生开发、打包工具 dist/esm 产物。

示例

//math.js
export { add: (left, right) => left + right; }

// 在calculator.js导入

import { add } from './math.js';
console.log('1 + 1 = ' + add(1, 1));

// ES6 Module例子
//calculator.js
var count=0;
const ADD=function(a,b){
      count+=1;
      return a+b;
};
export {count,ADD}
 
 
//inde.html
<script type="module">
 import {count,ADD} from './src/calculator.js'; // 导入calculator.js
 console.log(count); // 0
 ADD(2,3);
 console.log(count); // 1
</script>

// ES6模块是动态引用,如果使用import从一个模块中加载变量(即import foo from ‘foo'),那么,变量不会被缓存,而是成为一个指向被加载模块的引用,需要开发者保证在真正取值的时候能够取到值。
// 循环加载 
// a.js
import {bar} from './b.js';
export function foo() {
  console.log('foo');
  bar();
  console.log('执行完毕');
}
foo();
 
// b.js
import {foo} from './a.js';
export function bar() {
  console.log('bar');
  if (Math.random() > 0.5) {
    foo();
  }
}

//在浏览器中使用原生的 ESM
// 1 通过  ​​script[type="module"]​​   可直接在浏览器中使用原生 ESM
<script type="module">
        import arrayUniq from "https://cdn.jsdelivr.net/npm/array-uniq/index.js"

        console.log(arrayUniq([1, 2, 3, 2, 3]))
        // [1, 2, 3]
</script>

// 2 使用 ​​ script[type="importmap"]​​  统一配置导入路径
<script type="importmap">
    {
      "imports": {
        "array-uniq": "https://cdn.jsdelivr.net/npm/array-uniq/index.js"
      }
    }
</script>

<script type="module">
    import arrayUniq from "array-uniq"
    console.log(arrayUniq([1, 2, 3, 2, 3]))
    // [1, 2, 3]
</script>

// 3 使用 ​​assert​​ 指定导入文件的类型
<script type="module">
    import data from "./data.json" assert { type: "json" };
    console.log(data)
    // {name: 'Tom', age: '25'}
</script>
// data.json
{
    "name": "Tom",
    "age": "25"
}


七、五大模块规范横向总对比表

规范全称设计环境加载方式核心API代表工具当前状态
CommonJS通用 JS 模块Node 服务端同步阻塞require / module.exportsNode、Webpack CJS后端 / 旧库仍在用
AMD异步模块定义浏览器异步、依赖前置define / requireRequireJS已淘汰
CMD通用模块定义浏览器异步、依赖就近define + 内部 requireSeaJS完全淘汰
UMD通用模块封装全环境兼容自适应判断自执行环境判断第三方类库打包仅库产物使用
ESMES6 原生模块浏览器 + Node静态同步 + 动态异步import / exportVite、Webpack、原生浏览器现代唯一主流标准

八、工程化打包产物对应规范说明

九、模块化发展演进路线

无模块化(全局变量混乱)

现在常用的模块规范一般就是es6模块和commonjs(只用于node)了, node中也已经提供了实验性的es模块支持.

到此这篇关于一文带你搞懂JS五大模块标准:CommonJS / AMD / CMD / UMD / ESM的文章就介绍到这了,更多相关JS五大模块标准内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文