2

a.js和b.js互相require怎么执行? —— CommonJS模块探究

学习Node.js遇到2个模块互相引用的问题,有点搞不清楚。我觉得这个模块机制对于学习Node.js来说,搞清代码执行顺序来说,十分有必要。

所以在读了相关资料,并实践之后,愉快的在这分享一下成果。

CommonJS中模块的"循环引用"问题

先说结论: CommonJS中的做法时,一旦某个模块被”循环引用“,也就是这个模块没有加载完,就进入了循环,所以原则是,只exports已经执行的那部分,没执行的不输出。

如果没看懂,没关系。我第一遍也没明白。但是看下这个例子就好理解啦~

a.js

module.exports.done = false
var b = require('./a.js')
console.log(`在a模块中,b.done=${b.done}`)
module.exports.done = true
console.log('a模块执行完毕')
b.js
module.exports.done = false
var b = require('./a.js')
console.log(`在a模块中,b.done=${b.done}`)
module.exports.done = true
console.log('a模块执行完毕')

在终端执行

node a.js

下面我们梳理代码的执行过程。然后自己运行代码验证一下就好啦~

  • 1.a模块第1行导出done = false;
  • 2.第2行require('./b.js'),于是a模块暂停,进入并执行b模块
  • 3.b模块第1行导出done = false,之后require('./a.js'),此时出现"死循环",关键是CommonJS会如何处理呢? 请看上面的结论,这里的a模块还没执行完,仅仅执行了一句module.exports.done = false,所以就导出的就是这个。
  • 4.之后b模块执行完后,跳回到a模块第3行,然后依次打印。
终端的结果

clipboard.png

所以问题的关键,就是弄清楚,当a模块还没执行完成时,b模块里require a模块的话,执会导出a模块已经执行完成的部分。

上面的例子中,a模块只执行了

module.exports.done = false

所以就导出了这个。

那么建议同学们,试一下把require('./b.js')放到第一行,试试看执行结果会有什么不同?

如果有同学实践后,还是不理解的话,可以看看参考资料里阮一峰老师的文章,或者看一眼我这篇文章的第二个部分,也许缺少一些CommonJS的其他知识。

CommonJS中需要知道的一些知识点

  • 知识点1:require('./a.js')时,不仅仅是引用方法,如果是第一个require的话,就直接执行a.js了
  • 知识点2:再次require('./a.js')并不会有反复执行a.js的BUG,而是直接从缓存中取值。

被缓存的模块,可以通过require.cache里看到

  • 知识点3:module.exports和exports的区别
// 简单说,require只认module.exports,
// 而初始化时 exports是module.exports的引用

exports = module.exports
  • 知识点4:CommonJS里发生"循环引用"的话,就exports已经执行的部分。ES6发生"循环引用"的话,每次import只生成一个模块引用,不影响使用变量
 关于CommonJS里"循环引用"的解决方法
    + 办法1:module.exports = () => {const moduleDemo = require('./b.js')} 避免require时直接执行,使其延迟执行。
    + 办法2:把module.exports提到第一行,但不是万能的,因为要导出的东西可能依赖文件下面的计算

CommonJS与ES6模块中的"循环引用"的区别

每一个模块化方案,都可能会遇到"循环引用"的情况,但是执行方式不一样。

因为AMD和CMD规范将来会被淘汰,所以ES6和CommonJS更值得拿出来研究一下。

ES6的引入命令是import,例如

import {foo} from './some.js'

ES6引入foo时,不会执行./some.js,而是生成一个该模块的引用,当需要用foo变量时,在到some模块里取值。

所以ES6是动态引用,不会缓存some.js,而是生成一个引用对象。

在import时不去加载。自然地,所谓"循环引用"也就不是问题了。

参考资料
http://www.ruanyifeng.com/blo...


Ziwei
4.1k 声望420 粉丝

前端程序员