CommonJS 模块的循环加载
CommonJS 的做法是,一旦出现某个模块被 " 循环加载 ",就只输出已经执行的部分,还未执行的部分不会输出
CommonJS 模块的重要特性是加载时执行,即脚本代码在 require 的时候,就会全部执行。一旦出现某个模块被 " 循环加载 ",就只输出已经执行的部分,还未执行的部分不会输出。
让我们来看,Node 官方文档 里面的例子。脚本文件 a.js 代码如下。
exports.done = false;
var b = require('./b.js');
console.log('在 a.js 之中,b.done = %j', b.done);
exports.done = true;
console.log('a.js 执行完毕');上面代码之中,a.js 脚本先输出一个 done 变量,然后加载另一个脚本文件 b.js。注意,此时 a.js 代码就停在这里,等待 b.js 执行完毕,再往下执行。
再看 b.js 的代码。
exports.done = false;
var a = require('./a.js');
console.log('在 b.js 之中,a.done = %j', a.done);
exports.done = true;
console.log('b.js 执行完毕');上面代码之中,b.js 执行到第二行,就会去加载 a.js,这时,就发生了 " 循环加载 "。系统会去 a.js 模块对应对象的 exports 属性取值,可是因为 a.js 还没有执行完,从 exports 属性只能取回已经执行的部分,而不是最后的值。
a.js 已经执行的部分,只有一行。
exports.done = false;因此,对于 b.js 来说,它从 a.js 只输入一个变量 done,值为 false。
然后,b.js 接着往下执行,等到全部执行完毕,再把执行权交还给 a.js。于是,a.js 接着往下执行,直到执行完毕
ES6 模块的循环加载
1. /*a.js*/
2. import { count } from './b.js'
3. console.log(count);
4. export let message = 'hello'
5. /*b.js*/
6. import { message } from './a.js'
7. export let count = 5;
8. setTimeout(() => {
9. console.log(message);
10. }, 0);调用流程:
(1) 程序先进入 a.js,执行 import {count} from 'b.js',进入 b.js;
(2)b.js 中执行 import {message} from 'a.js',企图再次进入 a.js,但是 a.js 已经请求过,但没有解析完,被标记为 Fetching,
- (内部有一个 Module Map,专门记录一个 Module 当前的状态,如果解析完成就获取它的 Module Record(类似 AST,会分析出该模块的 import,export,获得依赖关系);如果没有解析完成,则被标记为 Fetching,不做处理,继续执行。)
此时从 a.js 中没有任何导出,无法获取 message(可以认为此时 message 为 undefined)。
(3)b.js 执行完毕,导出了 count,在 a.js(b.js 的上层)中找到 count,将它们链接起来 (指向同一个地址)
(4) 返回 a.js 中继续执行,导出了 message,在 b.js(a.js 的上层)中找到 message,将它们链接起来 (指向同一个地址)
(5)b.js 中的 setTimeout 执行,得到了 a.js 中导出的 message
区别
ES module 趋向于构建依赖树,它会沿着一个入口,根据 import 关系 (利用 AST 分析) 去构建一棵依赖树,遍历到树的叶子模块后,然后根据依赖关系,反向(向上)找到父模块,将 export/import 指向同一地址。
而 commonJS 的导出则简单的多,它将每个模块的导出视为一个对象,在刚进入模块的时候,就为它准备好了一个空对象作为它的导出结果,如果有导出就在这个对象上增加 key,value。因此,别的模块得到的引用对象则仅仅只是这个导出对象的引用。