CommonJS规范
Node.js 应用模块化使用的是 CommonJS ,每一个文件就是一个模块,拥有自己的作用域、变量以及方法,对其他的模块不可见。
从 Node.js v13.2 版本开始,Node.js 已经默认打开了 ES6 模块支持。但是要求 ES6 模块采用 .mjs 后缀名。 Node.js 遇到 .mjs 文件,就认为它是 ES6 模块,默认启用严格模式,不必在每个模块文件顶部指定 use strict。
总结:.mjs 文件总是以 ES6 模块加载,.cjs 文件总是以 CommonJS 模块加载,.js 文件的加载取决于 package.json 里面 type 字段的设置。
CommoJS 规范加载模块是同步的。
module对象
Node.js 在运行某些模块的时会自动创建一个 module 对象,同时会给 module 对象添加一个 exports 对象,初始化的值为 {}
module.exports = {}
Node.js在模块里可以直接调用 exports 和 module.exports 两个全局变量,但是 exports 只是 module.exports 的一个引用。
exports 的使用
// add.js
function add(a, b){
return a + b;
}
exports.plus = plus;
其实等价于
module.exports = {
plus: plus
}
// 在main.js中引用add.js
let Add = require('add.js')
Add.add(1, 2) // 3
module.exports 的使用
// add.js
function add(a, b){
return a + b;
}
module.exports = add;
// 在main.js中引用add.js
let add = require('add.js')
add(1, 2) // 3
require 语法的加载机制
require 命令用于加载模块文件。require 命令的基本功能是读入并执行一个 js 文件,然后返回该模块的exports 对象,如果没有发现指定模块,会报错。require 方法默认读取 js 文件,所以可以省略 js 后缀名。
Node.js 中第一次加载某个模块时,Node.js 会缓存该模块。以后再加载该模块,就直接从缓存取出该模块的module.exports 属性,不存在动态更新。
这里有几个需要注意的点:
exports只是module对象中exports方法的引用require引用模块后,返回的是module.exports而不是exports
ES6中的模块化(import、export、export default)
在一个文件中,export、import 可以有多个,但是 export default 只能有一个。
export的描述及使用
export 命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系。export 语句输出的接口,与其对应的值是动态绑定关系,即通过该接口,可以取得模块内部的实时的值。,也就是说,当导入对象在模块内值发生变化后,import 导入的对象也会相应的同步变化。
// add.js导出
export var 变量 = 值
export { 变量 }
export { 变量 as 输出的变量 }
// 引用
import { 变量 } from 'add.js'
export default(默认导出)的语法
上面的 export 是动态绑定关系。import default 却不是动态的。默认导出的导出结果是值而不是引用。
原因是默认导出可以看作一种对“default赋值”的特例,本质上是一种赋值,所以拿到是值而不是引用。
// add.js导出
export default {
add(){ }
}
export default var count = `1`
// 引用
import add from 'add.js'
只要是使用export default导出的都是值而不是引用!除了以下的特例:
// 导出的是引用
export default function load() {
console.log('doing something')
}
export default function 是一种特例,这种写法会导致导出的是引用而不是值。
如果我们用正常的方式导出Function,那么导出的还是值,哪怕导出的对象是个function。
// 导出的是值
function onload() {}
export default onload;
import 命令和动态 import
import 命令输入的变量都是只读的,因为他的本质是输入接口。不允许在加载模块的脚本里面,改写接口。
标准用法的 import 导入的模块是静态的,会是所有被导入的模块,在加载时就被编译。在有些场景中,我们希望根据条件导入模块或者按需导入模块,我们可以使用动态导入代替静态导入。
关键字 import 可以像调用函数一样来动态导入模块。以这种方式,将返回一个 promise。
// add.js
export let value = 'oldValue';
import('./add.js').then((module) => {
// Do something with the module.
})
// 或者是
let module = await import('./add.js')
let { value } = await import('./add.js')
console.log(module.value)
console.log(value)
通过上文我们知道, export 导出的是引用。那么我们在使用 import 命令行导入 export 导出的对象一定是引用吗?
答案是否定的。对于导入来说,{} = await import() 相当于重新赋值,所以具体对象的引用会丢失,也就是说异步的导入会重新赋值。而 let module = await import()引用不变的原因是 module 本身是一个对象,module.value 的引用还是不变的,即便 module 是被重新赋值的。
AMD 规范
AMD 的全称是 Asynchronous Module Definition,即异步模块加载机制。
基于 CommonJS 规范的 Node.js 出来以后,服务端的模块概念已经形成,很自然地,大家就想要客户端模块。
CommonJS 规范因为同步加载的,如果加载的资源较大就会导致加载时间过长,那么整个应用就会停在那里等待。这对于服务器端不是一个问题,因为所有的模块都存放在本地硬盘,可以同步加载完成,等待的时间就是硬盘的读取时间。
但是对于浏览器来说却是一个大问题,因为模块都放在服务器端,等待的时间取决于网速的快慢,可能要等很长的时间,所以浏览器端不能使用“同步加载”。
为了解决这种问题,只能采用异步加载。这就是 AMD 规范诞生的背景。AMD 定义了一套 JavaScript 模块依赖异步加载标准,来解决同步加载的问题。
AMD 是依赖前置的,换句话说,在解析和执行当前模块之前,模块作者必须执行当前模块所以来的模块,表现在require 函数的调用结构上为:
define(['./a', './b'], function(a, b) => {
a.doSomething();
b.doSomething();
})
举例: 模块通过define函数定义在闭包中,格式如下
define(id?: string, dependencies?: string[], factory: Function | Object)
dependencies 指定了所要依赖的模块列表,它是一个数组,也是可选的参数,每个依赖的模块的输出将作为参数依次传入factory中。如果没有指定dependencies,那么它的默认值是["require", "exports", "module"]
举例: 定义一个 myModule 的模块,它依赖 jQuery 模块。使用 define 和 require 需要引入 require.js 文件,文件地址: requirejs.org/docs/releas…
<scritpt src='./require.js' ></script>
define('myModule', ['jquery'], function($) {
$('body').text('hello world');
return true;
})
// 使用
require(['myModule'], function(myModule) {
console.log(myModule) // true
})
需要注意的是,异步加载依赖项应使用数组来列出依赖项
require(['myModule']) //true
require('myModule') // false
目前,主要有两个 JavaScript 库实现了 AMD 规范:require.js 和 curl.js
CMD 规范
CMD 是 seajs 推崇的规范,CMD 则是依赖就近,用的时候再 require。格式如下
define(id?: string, dependencies?: string[], factory: Function | Object)
CMD 和 AMD 最大的区别是对依赖模块的执行时机处理不同,而不是加载的时机或者方式不同,二者皆为异步加载模块。
AMD 依赖前置,js可以方便知道依赖模块是谁,立即加载。
CMD 就近依赖,需要使用把模块变为字符串解析一遍才知道依赖了哪些模块。
UMD 模块规范
UMD 的全称是 Universal Module Definition。
UMD 并不能说是一种模块标准,不如说它是一组模块形成的集合更准确,也就是通用模块标准。
它的目标是使一个模块能运行在各种环境下,不论是 CommonJS、AMD,还是非模块化的环境。既可以在 node/webpack 环境中被 require 引用,也可以在浏览器中直接用 CDN 被 script 标签引入。
参考文章: 精读《默认、命名导出的区别》