CommonJS中require的循环依赖理解

Nodejs 官网的一个循环依赖的例子

a.js

1
2
3
4
5
6
console.log('a starting');
exports.done = false;
const b = require('./b.js');
console.log('in a, b.done = %j', b.done);
exports.done = true;
console.log('a done');

b.js

1
2
3
4
5
6
console.log('b starting');
exports.done = false;
const a = require('./a.js');
console.log('in b, a.done = %j', a.done);
exports.done = true;
console.log('b done');

index.js

1
2
3
4
console.log('main starting');
const a = require('./a.js');
const b = require('./b.js');
console.log('in main, a.done = %j, b.done = %j', a.done, b.done);

运行node index.js后输出顺序如下

1
2
3
4
5
6
7
8
main starting
a starting
b starting
in b, a.done = false
b done
in a, b.done = true
a done
in main, a.done = true, b.done = true

CommonJS规范的执行逻辑如下

  1. 执行 main.js -> main starting

  2. require(‘./a.js’) -> 执行a.js -> a starting

  3. exports.done = false -> 内存中针对a模块对应对象

    1
    2
    3
    4
    5
    6
    {
    id: '模块名a',
    exports: {done: false},
    loaded: false,
    ...
    }
  4. require(‘./b.js’) -> 执行b.js -> a.js代码暂停 -> b starting

  5. exports.done = false; -> 内存中针对b模块对应对象

    1
    2
    3
    4
    5
    6
    {
    id: '模块名b',
    exports: {done: false},
    loaded: false,
    ...
    }
  6. reuqire(‘./a.js’) -> 加载a.js 发生循环依赖 -> a 模块对应的模块对象的exports.done: false -> in b, a.done = false

  7. 继续执行b.js exports.done = true -> b done

    1
    2
    3
    4
    5
    6
    {
    id: '模块名b',
    exports: {done: true},
    loaded: true,
    ...
    }
  8. 继续执行a.js -> in a, b.done = true

  9. 执行 a.js exports.done = true -> b done

    1
    2
    3
    4
    5
    6
    {
    id: '模块名a',
    exports: {done: true},
    loaded: true,
    ...
    }
  10. main.js in main, a.done = true, b.done = true

CommonJS规范如何处理循环加载

CommonJS模块的重要特性就是加载时执行, 当脚本require时, 就会全部执行; 并且在内存中生成一个对象, 遇到某个模块循环加载, 则只输出已经执行的部分, 还未执行的部分不会输出.

reuqire()源码解读
http://www.ruanyifeng.com/blog/2015/05/require.html

1
2
3
4
5
6
{
id: '模块名',
exports: {}, // 以后用到该模块就会取在exports的值, 不会重新执行模块.
loaded: true, // 表示该模块的脚本是否执行完毕
...
}

webpack 对require 的编译如下:

编译后源码完整
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
(function(modules) {
var installedModules = {};
function __webpack_require__(moduleId) {
if (installedModules[moduleId]) {
return installedModules[moduleId];
}

var module = installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {}
}
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
module.l = true;
return module.exports;
}

__webpack_require_.d = function(exports, name, getter) {
if(!__webpack_require__.o(exports, name)) {
Object.defineProperty(exports, name, { enumerable: true, get: getter });
}
};

__webpack_require__.r = function(exports) {
if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
}
Object.defineProperty(exports, '__esModule', { value: true });
};

...
return __webpack_require__(__webpack_require__.s = "./server/index.js");
})({
"./server/a.js": (function(module, exports, __webpack_require__) {
console.log('a starting');
exports.done = false;
const b = __webpack_require__("./server/b.js");
console.log('in a, b.done = %j', b.done);
exports.done = true;
console.log('a done');
}),
"./server/b.js": (function(module, exports, __webpack_require__) {
console.log('b starting');
exports.done = false;
const a = __webpack_require__("./server/a.js");
console.log('in b, a.done = %j', a.done);
exports.done = true;
console.log('b done');
}),
"./server/index.js": (function(module, exports, __webpack_require__) {
console.log('main starting');
const a = __webpack_require__("./server/a.js");
const b = __webpack_require__("./server/b.js");
console.log('in main, a.done = %j, b.done = %j', a.done, b.done);
})
})