# CommonJs - ES module

# CommonJs

# 背景

  • ES6之前,ECMAScript 没有提供代码组织的方式,那时候通常使用 IIFE 来实现模块化,
    随着 JavaScript 的发展,原本浏览器端的模块规范不利于大规模利用,于是便有了 CommonJS 规范

# 作用

  • 为了定义模块,提供通用的模块组织方式。

# 模块定义和使用

  • 一个文件就是一个模块,导出通过 exports 或者 module.exports 挂载。
exports.count = 1;
1
  • 导入模块 require
const counter = require('./counter');
console.log(counter.count);
1
2
  • CommonJs 的模块主要由原生模块 module 实现
Module {
  id: '.', // 如果是 mainModule id 固定为 '.',如果不是则为模块绝对路径
  exports: {}, // 模块最终 exports
  filename: '/absolute/path/to/entry.js', // 当前模块的绝对路径
  loaded: false, // 模块是否已加载完毕
  children: [], // 被该模块引用的模块
  parent: '', // 第一个引用该模块的模块
  paths: [ // 模块的搜索路径
   '/absolute/path/to/node_modules',
   '/absolute/path/node_modules',
   '/absolute/node_modules',
   '/node_modules'
  ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 模块的查找过程

  • 在创建模块对象时,会有 paths 属性,其值是由当前文件路径计算得到的,从当前目录一直到系统根目录的 node_modules
[ 
  '/Users/evan/Desktop/demo/node_modules',
  '/Users/evan/Desktop/node_modules',
  '/Users/evan/node_modules',
  '/Users/node_modules',
  '/node_modules'
]
1
2
3
4
5
6
7
  • 除此之外 还会查找全局路径
[
  execPath/../../lib/node_modules, // 当前 node 执行文件相对路径下的 lib/node_modules
  NODE_PATH, // 全局变量 NODE_PATH
  HOME/.node_modules, // HOME 目录下的 .node_module
  HOME/.node_libraries // HOME 目录下的 .node-libraries
]
1
2
3
4
5
6

# 缓存和循环引用

  • 文件模块的查找挺耗时的,如果每次 require 都需要重新遍历文件夹查找,性能会比较差。
    CommonJs 的缓存可以解决重复查找和重复执行的问题,模块加载过程中会以模块绝对路径为 key,
    module 对象为 value 写入 cache。在读取模块时会优先判断是否已经存在缓存中,如果在,
    直接返回 module.exports ,否则进入模块查找的流程,找到模块后再写入 cache。

# ES Module

  • 使用 import export 关键字来进行模块输入输出。
    ES6模块代码最终会被 babel TypeScript 等工具处理成 CommonJs

# 加载过程

  1. 查找,下载,解析,构建所有模块实例
  • ES6模块会在程序开始前先根据模块关系查找所有的模块,生成一个无环关系图,并将所有的模块实例 都创建好,这样避免了循环引用的问题,当然也有模块加载缓存,重复 import 同一个模块,只会执行一次代码。
  1. 在内存中腾出空间给即将 export 的内容(此时尚未写入 export value),然后使 import 和 export 指向 内存中的这些空间,这个过程也叫连接。
  • 这一步完成工作的是 living binding import export
  1. 运行模块代码,将变量的实际值写在第二步生成的空间中

# 差异点

  1. CommonJs 可以在运行时使用变量进行 require ,如 require(path.join('xxx', 'xxx.js')) 而静态 import 语法(还有动态 import ,返回 Promise)不行,因为 ES6 模块会先解析所有模块再执行代码。

  2. require 引入的是完整的 exports 对象,import 可以只 import 部分需要的内容。

  3. import 另一个模块没有的 export 的变量,在代码执行前就会报错,而 CommonJs 是在模块运行时才报错。