CommonJS与ES6 Module本质区别,commonJS对模块依赖解决是“动态的”,ES6 Module是静态的node
module | 模块依赖 | 含义 | 备注 |
---|---|---|---|
CommonJS | 动态 | 模块依赖关系的创建发生在代码运行阶段 | node命令执行es6混用 .cjs |
ES6 Module | 静态 | 模块依赖关系创建发生在代码编译阶段 | node命令执行es模块 --experimental-modules |
// example-1/test.cjs
module.exports = { name: 'test' };
// example-1/index.cjs
const { name } = require('./test.js');
复制代码
CommonJS规范,当模块A加载模块B时,例如上面index.js加载test.js,会执行test.js中的代码, module.exports对象会做为require函数的返回值被加载。require的模块路径能够动态指定,支持 传入一个表达式,也能够经过if语句判断是否加载某个模块。所以在CommonJS模块被执行前,并不 能明确依赖关系,模块的导入导出发生在代码运行时。webpack
// example-1/test.js
export const name = 'test';
// node example-1/index.js
import { name } from './test.js';
复制代码
ES6 Module的导入导出都是声明式的,它不支持导入路径是一个表达式,全部导入导出 必须位于模块的顶层做用域(不能放在if语句中)。所以ES6 Module是一个静态的模块结构,在ES6 代码编译阶段就能够分析出模块的依赖关系。git
导入模块时,CommonJS是导出值的拷贝,ES6 Module是值的动态映射,而且这个映射是自读的。es6
index.cjs中count是对test中的count的值拷贝,所以在调用add时,改变了test中的count,可是不会对index中的 count形成影响github
// example-2/test.cjs
var count = 0;
module.exports = {
count,
add () {
count++;
return count;
}
}
// example-2/index.cjs
let { count, add } = require('./test.cjs');
console.log(count); // 0 这里的count是对test.js中的count的拷贝
add();
console.log(count); // 0 test.js中值改变不会形成index中的拷贝值影响
count += 1;
console.log(count); // 1 index中拷贝值改变
复制代码
ES6 Module中导入的变量时对原有值的动态映射,index中调用add,count也会变化,咱们不能对ES6 Module 导入的变量进行更改web
// example-2/test.js
let count = 0;
export {
count,
add () {
count++;
}
}
// example-2/index.js
import { count, add } from './test.js';
console.log(count); // 0
add();
console.log(count); // 1
count++; // TypeError: Assignment to constant variable.
复制代码
循环依赖指模块A依赖于模块B,同时模块B依赖于模块A(工程中应该尽可能避免循环依赖,复杂度会提高,依赖关系不清晰), 若是A依赖B,B依赖C,C依赖D......最后一圈回来D又依赖A,那么多中间模块之间的依赖关系就很难梳理了bash
// example-3/a.cjs
const b = require('./b.cjs');
console.log('b:', b);
module.exports = 'a.cjs';
// example-3/b.cjs
const a = require('./a.cjs');
console.log('a:', a);
module.exports = 'b.cjs';
// example-3/index.cjs
require('./a.cjs');
复制代码
上面的例子,a依赖b,b依赖a函数
// 预期输出
a: a.cjs
b: b.cjs
// 实际输出
a: {}
b: b.cjs
复制代码
实际输出时a是一个空对象工具
webpack实现__webpack_require__,初始化一个module对象放入installedModules中,当这个模块再次 被引用到时直接从installedModules里面取值,此时他就是一个空对象,解释了上面例子的现象优化
function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
复制代码
ES6 Module方式实现循环依赖,下面的例子运行时报错了,没法在初始化以前访问"a"
// example-3/a.js
import b from './b.js';
console.log('b:', b);
export default 'a.js';
// example-3/b.js
import a from './a.js';
console.log('a:', a);
export default 'b.js';
// example-3/index.js
import a from './a.js'; // ReferenceError: Cannot access 'a' before initialization
复制代码
改一改上面的例子,使其能正常运行
// example-4/a.js
import b from './b.js';
function a (invoker) {
console.log(invoker + 'invokes a.js');
b('a.js');
}
export default a;
// example-4/b.js
import a from './a.js';
function b (invoker) {
console.log(invoker + 'invokes b.js');
a('b.js');
}
export default b;
// example-4/index.js
import a from './a.js';
a('index.js');
// 执行结果
index.jsinvokes a.js
a.jsinvokes b.js
b.jsinvokes a.js
a.jsinvokes b.js
复制代码
上面的例子正确的打印了a与b的循环依赖,来分析下代码执行过程