模块化开发
CommonJS
以同步模式加载模块,运行时加载,因此动态决定加载内容。
let path = './utils'
if(condition){
path += '/add.js'
}else{
path += 'delete.js'
}
const utilsFn = require(path)
而 ESM 在编译时加载,也就是执行 import 时并没有执行上下文的代码。
let path = './utils'
if(condition){
path += '/add.js'
}else{
path += 'delete.js'
}
// 报错, 因为在加载下面模块时上面的代码并没有被执行。
import utilsFn from `${path}`
CommonJS 顶层 this 指向本模块。
ES Modules
特性
- 自动采用严格模式
- 顶层 this 指向 undefined
- 每个 ESM 模块都是单独的私有作用域
- ESM 通过跨域请求外部 JS 模块,普通的 script 标签请求即使跨域也不会被浏览器拦截,但 ESM 下会被拦截。
- ESM 的 script 标签会异步加载脚本,相当于加上 defer 属性
- import 命令输入的变量只读,不能修改。但是导出的对象的属性可以修改,因为多个导入该对象的模块引用同一个对象,所以一处修改会影响到模块,所以最好不要修改导入对象的属性
- script 标签 使用 ESM 后,即
type=module
src
指向的资源不能跨域加载。普通情况下script
标签可以跨域加载资源。
ES Modules 导出 导入
export
导出的是只读的引用。多个文件同时 import 一个变量时,多个文件引用的一个同个变量。
所以 Vue2 中组件的 data 选项需要一个函数返回一个新的对象而不是直接给一个对象。 // ?
特殊的 import()
import() 可以实现动态加载,比较类似 require, 但两者却不同。 require 使用同步加载,加载时会阻塞 JS 代码的运行,而 import() 返回 Promise 对象。
// CommonJS
const utils = require('somePath') // 只有该模块加载完后才会执行下面的代码
console.log(1)
// import()
if(condition){
import('pathOne').then(module => {
// do something
})
}else{
import('pathTwo').then(module => {
// do something
})
}
import() 适用于按需加载、动态加载、条件加载的场景。
CommonJS 与 ES Modules 对比
主要的差别
ESM 为静态加载,即可以在编译时就完成模块加载。CommonJS 为动态加载,在程序运行时才能获得加载的对象。
CommonJS 导出一个对象的拷贝,ESM 导出值的引用。
CommonJS 的 require() 同步加载模块,ESM import 异步加载模块,有一个独立的模块依赖的解析阶段。!
循环加载
CommonJs遇到循环加载时被加载模块只会被部分执行导入的只是模块部分执行的结果。
test1.js
exports.value = true;
require('./test2.js');
exports.value = false;
test2.js
const md1 = require('./test1.js');
console.log('md1.value in test2.js', md1.value);
test2.js 中输出的 value
为 true
因为在 test2 中循环加载 test1 时并不会运行整个 test1 模块,而是在当前执行下获取导出值。
ES Modules import 导入时并没有执行模块的代码,而是给一个引用,直到真正执行代码时才会调用相关引用。所以循环引用时只是引用的一个对象,不会循环的执行引用模块。