What are Modules?

Good authors divide their books into chapters and sections; good programmers divide their programs into modules.

Benefits of using modules

Using modules in favor of sprawing, interdependent codebase.

  • Maintainability
    Updating a single module will be much more easier when the module is decoupled from other pieces of codebase.
  • Reusability
    A module that we can reuse over and over again without ctrl+v all around.

Incorporate modules into programs

  • the common approaches is use a single global variable to wrap code in a function, thereby creating a private namespace for itself using a closure scope.
  • if your programs relys on many other dependencies, you need to know the right order to load files in, that is troublesome.
  • loading other dependencies can still lead to namespace collisions. For examples, what if two of your modules have the same name? Or waht if you have two versions of module, and you need both? The CommonJS and AMD are the answer!

CommonJS

Note that CommonJS takes a server-first approach and synchronously loads modules. this looks great on the server but, unfortunately, makes it harder to use when writing JS for browser Reading a module from the web takes a lot longer than reading from disk. For as long as the script to load a module is running, it blocks the browser from running annything else until it finishes the loading. It behaves the way because the JS thread stops until the code has been loaded.

AMD

Shorhands of Asynchronous Module Definition

define(['moduleA', 'moduleB'], function(moduleA, moduleB) {
  moduleA.hello();
})
moduleA.js
define("moduleA",[], function() {
  return {
    hello: function() {
      console.log('hello')
    }
  }
})

more resource

ES6 modules

Compare to CommonJS, imports in ES6 modules are live

counter.js
var count = 1;
function increment() {
  count++;
}
function decrement() {
  count--;
}
module.exports = {
  count: count,
  increment: increment,
  decrement: decrement
};
main.js
var counter = require('./counter.js');
 
counter.increment();
console.log(counter.count); // 1

How is it possible be one?🤔Because the count variable we imported is a disconnected copy and incrementing the count will increment it in the module(function is't primitive value, it is just a reference), but won't increment the copied version.

// ./counter.js
export let count = 1;
export function increment() {
  count++;
}
export function decrement() {
  count--;
}
// ./main.js
import * as counter from './counter';
 
console.log(counter.count); // 1
counter.increment();
console.log(counter.count); // 2

The imports of an ES6 module are read-only views on the exported entities. , which means there are following advantages

  • They enable cyclic dependencies(two modules depend on each other), even for unqualified imports.
  • Qualified and unqualified imports work the same way (they are both indirections).
  • You can split code into multiple modules and it will continue to work (as long as you don't try to change the values of imports).

The Most important things, ES6 Modules are designed with static analysis. When you import modules, the import is resolved at compile time - that is, before the script starts executing. This allows us to remove exports that are not used by other modules before we run the program. Removing unused exports and can lead to significant space saving, reducing stress on the browser The approach to eliminate dead code, called "tree shaking"

Cyclic dependencies in CommonJS

//index.js
var a = require('./a')
var b= require('./b')
 
// a.js
module.exports.a = '原始值-a模块内变量'
console.log('a模块执行')
var c = require('./c')
 
// b.js
module.exports.b = '原始值-b模块内变量'
console.log('b模块执行')
var c = require('./c')
 
// c.js
module.exports.c = '原始值-c模块内变量'
console.log('c模块执行')

输出: a模块执行 c模块执行 b模块执行
为什么仅在第8行require后输出 “c模块执行” , 13行require后不再输出了
因为第一次执行c模块, CommonJS会将它缓存起来, 相当于拷贝一份, 放在一块新的内存中

扩展文章

What require() does

Cyclic dependencies in ESModule

ES module导出的是一个索引——内存地址, 没有办法这样处理. 在代码执行前, 首先要进行预处理, 这一步会根据import和export来构建模块地图Module Map, 它类似于一颗树, 树中的每一个“节点”就是一个模块记录, 这个记录上会标注导出变量的内存地址, 将导入的变量和导出的变量连接, 即把他们指向同一块内存地址, 不过此时这些内存都是空的uninitialized

// index.mjs
import * as a from './a.mjs' //打断点debug -> step into -> 跳转到13行
console.log('入口引用a:',a)
 
// a.mjs
import * as b from "./b.mjs"
let a = "AAA"
console.log("a引用b:", b)
export { a }
 
// b.mjs
import * as a from "./a.mjs"
let b = "BBB"     // 由第2行跳转过来
console.log("b引用a:", a) //没打印出来
export { b }
  • 打印如下
webp
  • .vscode目录下launch.json配置
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Launch Program",
      "program": "${workspaceFolder}/test/cyclic_dependencies/index.mjs", //workspaceFolder是当前打开的最上层目录
      "request": "launch",
      "skipFiles": [
        "<node_internals>/**"
      ],
      "type": "node",
      "runtimeExecutable": "/usr/local/bin/node"
    },
  ]
}