# 前端模块化

CommonJSAMDCMD都是社区对模块定义的规范,而NodeJSRequireJSseaJS则分别是对这三种规范的实现。

以下按时间线了解这几种规范

# CommonJS

commonJs规范通过简单的 API 声明服务器的模块,目标是让 JavaScript 可以运行在浏览器之外的所有地方。Node.js 就是借鉴 CommonJs 实现了一套非常易用的模块系统,以及 NPM 对模块规范的完美支持是的 Node.js 应用开发事半功倍。

  • require:用来引用模块
  • export:用来导出模块
    • module.exoirts:对外导出一个对象
    • exports.xxx:可以对外导出多个对象

注意:核心是 module.exports,exports 只是 module.exports 的引用而已

在每个模块代码执行之前,Node.js 都会将它包裹在这样一个函数里

(function (exports, require, module, __filename, __dirname) {});

# JSON 文件

  • 通过 fs.readFileSync() 加载
  • 通过 JSON.parse() 解析

# 加载大文件

  • require 成功后会缓存文件
  • 大量使用会导致大量数据驻留在内存中,导致 GC 频分和内存泄露

# exports

  • exports 是 module 的属性,默认情况是空对象
  • require 一个模块实际得到的是该模块的 exports 属性
  • exports.xxx 导出具有多个属性的对象
  • module.exports = xxx 导出一个对象
// module-2.js
exports.method = function() {
  return 'Hello';
};

exports.method2 = function() {
  return 'Hello again';
};

// module-1.js
const module2 = require('./module-2');
console.log(module2.method()); // Hello
console.log(module2.method2()); // Hello again

# AMD 和 require.js

AMD 是为了弥补 commonjs 规范在浏览器中目前无法支持 ES6 的一种异步解决方案。异步模块定义规范(AMD)制定了定义模块的规则,这样模块和模块的依赖可以被异步加载。这和浏览器的异步加载模块的环境刚好适应(浏览器同步加载模块会导致性能、可用性、调试和跨域访问等问题)。

define("alpha", ["require", "exports", "beta"], function (
  require,
  exports,
  beta
) {
  exports.verb = function () {
    return beta.verb();
    //Or:

    return require("beta").verb();
    if (false) {
      // 即便没用到某个模块 ,还是提前执行了
      alpha();
    }
  };
});

# CMD 和 Sea.js

CMD 是另一种 js 模块化方案,它与 AMD 很类似,不同点在于:AMD 推崇依赖前置、提前执行CMD 推崇依赖就近、延迟执行

define(function (require, exports, module) {
  var a = require("./a"); //在需要时申明
  a.doSomething();

  if (false) {
    var b = require("./b");
    b.doSomething();
  }
});

# ESModule

ES 模块的目标是创建一个同时兼容 CommonJsAMD 的格式,使语法更加紧凑,通过编译时加载,在编译时就能确定模块的依赖关系,比 CommonJS 模块的加载效率更高。而在异步加载和配置模块加载方面,则借鉴 AMD 规范,执行效率、灵活度都远远好于 CommonJS 写法。总的来说,ES Module 的优势如下:

  • 语法更加紧凑
  • 结构更适用于静态编译(静态类型检查、优化等)
  • 对循环应用的支持更好。
  • 用法简单,不需要关注实现细节
  • 采用声明式语法:没有 require 关键字
  • 程序化加载 API:可以设置模块如何加载并按需加载

ES6 模块跟 Common 不一样,js 对脚本进行静态分析的时候,只生成一个只读引用,等到脚本真正执行的时候才会通过引用模块中获取值,所以 es6 是动态引用,并不会缓存值

关于循环引用:import 语句的静态加载语义意味着可以确保通过 import 相互依赖的 "foo" 和 "bar" 在其中任何一个运行之前,二者都会被加载、解析和编译。所以它们的环依赖是静态决议的.

我们应该大胆拥抱 ESModules,snowpackwebpacktree-shaking 通过静态编译进行大幅优化。

# CommonJS 与 ES6 Modules 规范的区别

  • CommonJS 模块是运行时加载,ES6 Modules 是编译时输出接口
  • CommonJS 输出是值的拷贝;ES6 Modules 输出的是值的引用,被输出模块的内部的改变会影响引用的改变
  • CommonJs 导入的模块路径可以是一个表达式,因为它使用的是 require()方法;而 ES6 Modules 只能是字符串
  • CommonJS this 指向当前模块,ES6 Modules this 指向 undefined
  • 且 ES6 Modules 中没有这些顶层变量:argumentsrequiremoduleexportsfilenamedirname
Last Updated: 12/22/2022, 9:53:26 AM