模块化规范 CommonJS、AMD、CMD、ES6

34 阅读3分钟

Github 仓库案例

1、CommonJs 规范

  • 每个文件都是一个独立模块,特性是运行时加载、同步加载、值的拷贝
  • 服务器端(Node)天然支持 CommonJS 规范
  • 浏览器端需要提前使用 Browserify 编译打包(处理require、exports)
  • 模块内属性
    • module 对象

      • module.id 模块的识别符,通常是带有绝对路径的模块文件名。
      • module.filename 模块的文件名,带有绝对路径。
      • module.loaded 返回一个布尔值,表示模块是否已经完成加载。
      • module.parent 返回一个对象,表示调用该模块的模块。
      • module.children 返回一个数组,表示该模块要用到的其他模块。
      • module.exports 对外的输出接口
    • require 加载其它模块, 实际上就是读取 module.exports 变量

      • require.resolve():将模块名解析到一个绝对路径
      • require.main:指向主模块require.main === module
      • require.cache:指向所有缓存的模块
      • require.extensions:根据文件的后缀名,调用不同的执行函数
    • exports 是 module.export 的别名( var exports = module.exports; )

      • 只能:exports.add = function (r){return Math.PI * r *r};
      • 不能:exports = function(x){console.log(x)};
    • __filename

    • __dirname

模块的加载机制值的拷贝
// lib.js
var counter = 3;
function incCounter() {
  counter++;
}
module.exports = {
  counter: counter,
  incCounter: incCounter,
};

// main.js
var counter = require('./lib').counter;
var incCounter = require('./lib').incCounter;

console.log(counter);  // 3
incCounter();
console.log(counter); // 3

2、AMD 规范

  • 用于浏览器环境、结合requireJs库使用
  • 异步加载模块
// html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <!-- src :requireJS 的入口 -->
    <!-- data-main :main文件必须在根目录下,指定网页程序入口文件/主模块 -->
    <script src="js/libs/require.js" data-main="main.js"></script>
  </head>
  <body></body>
</html>

// main.js
(function () {
  require.config({
    baseUrl: "js/", // 可以指定base路径
    paths: {
      alerter: "modules/alerter", // js/modules/alerter
      dataService: "modules/dataService", // js/modules/dataService
      // jquery 天然支持 AMD 规范,内部定义了  define( "jquery"...
      // if ( typeof define === "function" && define.amd ) {
      //   define( "jquery", [], function() {
      //     return jQuery;
      //   } );
      // }
      jquery: "libs/jquery.min", // js/modules/dataService
    },
  });

  // 引入 alerter 模块
  require(["alerter"], function (alerter) {
    alerter.showMsg();
  });
})();


// modules/alerter.js
// 定义一个有依赖的模块:引入 dataService 模块
define("alerter", ["dataService", "jquery"], function (dataService, $) {
  let msg = "alerter";

  function showMsg() {
    $("body").css("background", "red");
    console.log(msg, dataService.getName());
  }

  // 暴露模块
  return { showMsg };
});


// modules/dataService
// 定义一个没有依赖的模块
define("dataService", [], function () {
  let name = "dataService";

  function getName() {
    return name;
  }

  // 暴露模块
  return { getName };
});

3、CMD 规范

  • 用于浏览器环境结合 sea.js 库使用
  • 动态加载模块
  • 依赖模块加载完成后并不执行、遇到 require 时才执行
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>

    <script type="text/javascript" src="js/libs/sea.js"></script>
    <script type="text/javascript">
      // 入口文件
      seajs.use("./js/modules/main.js");
    </script>
  </head>
  <body></body>
</html>


// modules/main.js - 主入口文件
define(function (require, exports, module) {
  let module1 = require("./module1.js");
  console.log(module1.getName());

  let module4 = require("./module4.js");
  console.log(module4.getName());
});


// modules/module1.js - 定义没有依赖的模块
define(function (require, exports, module) {
  let name = "module1";
  function getName() {
    return name;
  }

  // 暴露模块
  module.exports = { getName };
});


// modules/module2.js
define(function (require, exports, module) {
  let name = "module2";
  function getName() {
    return name;
  }

  // 暴露模块
  module.exports = { getName };
});


// modules/module3.js
define(function (require, exports, module) {
  let name = "module3";
  function getName() {
    return name;
  }

  // 暴露模块
  module.exports = { getName };
});


// modules/module4.js
define(function (require, exports, module) {
  let name = "module4";
  function getName() {
    return name;
  }

  // 同步引入 module2
  let module2 = require("./module2");
  console.log(module2.getName());

  // 异步引入 module3
  require.async("./module3", function (module3) {
    console.log(module3.getName());
  });

  // 暴露模块
  module.exports = { getName };
});


4、ESM 规范

  • 浏览器端:使用 Babel 将 ES6+ 转为 ES5、使用 Browserify 编译打包js
  • 通常直接使用 webpack、vite 等打包工具处理使用
export:输出的必须是一个 对外接口( 比如 变量 )
  • export { m, fn } :导出的并非自变量对象
  • import { m, fn } :引用时也并非是结构赋值
  • 内部值改变,导致外面引用值的变化
  • 外部不能修改,导出的来的值(报错)
// 导出的并非自变量对象( 固定写法 )
// 若导出对象,使用 export default { m, fn }
export var m = 1; // 带名字变量
export function fn() {} // 带名字函数
export class Person {} // 带名字类

export { m, fn, Person }; // 同上

// 引用时也并非是结构赋值
import * as axios from "./profile.js";

export default:模块指定默认输出
  • 一个模块 export default 命令只能使用一次
  • 本质上输出一个叫 default 的变量或方法
  • export default 的本质是将后面的值赋给 default
//导出
export default a; // 变量的值
export default { name: 'zs', age: 20 } // 变量的值
export default function(){} // 匿名函数
import()
  • import() 函数可以用在 任何地方
  • 返回一个 Promise 对象
  • import()函数与所加载的模块没有静态连接关系
<script type="module">
    import("./module/a.js").then(res=>{})
</script>

// 条件加载
if (condition) {
    import('moduleA').then(...);
} else {
    import('moduleB').then(...);
}

// 按需加载
import('./dialogBox.js')
    .then(dialogBox => {
        dialogBox.open();
    })
    .catch(error => {
        /* Error handling */
    })
});

// 同时导入多个模块
Promise.all([
    import('./module1.js'),
    import('./module2.js'),
    import('./module3.js'),
])
.then(([module1, module2, module3]) => {
   ···
});