我的理论是这种行为是 node.jsrequire
缓存如何工作、node 如何处理循环依赖关系以及咖啡转译器在将.coffee
文件直接加载到 node 时如何工作的组合。
我制作了不同的coffeescript和javascript版本的程序,并带有如下说明性日志记录。我使用“maincs”和“mainjs”来确保没有混合两种语言。
mainjs.js
console.log("mainjs starting");
console.log("mainjs exporting name");
module.exports = {
name: 'John'
};
console.log("mainjs requiring configjs");
var configjs = require('./configjs');
console.log("mainjs calling configjs.hello()");
configjs.hello();
configjs.js
console.log("configjs starting");
console.log("configjs requiring mainjs");
var mainjs = require("./mainjs");
console.log("configjs exporting hello");
module.exports = {
hello: function() {
return console.log("hello " + mainjs.name);
}
};
maincs.coffee
console.log "maincs starting"
console.log "maincs exporting name"
module.exports =
name: 'John'
console.log "maincs requiring configcs"
configcs = require './configcs'
console.log "maincs calling configcs.hello()"
configcs.hello()
configcs.coffee
console.log "configcs starting"
console.log "configcs requiring maincs"
maincs = require "./maincs"
console.log "configcs exporting hello"
module.exports =
hello: ->
console.log "hello #{maincs.name}"
因此,当我们运行它们时,我们会得到不同的输出(如您所见)。我在下面强调了有趣的一点。
node mainjs.js
mainjs starting
mainjs exporting name
mainjs requiring configjs
configjs starting
configjs requiring mainjs #<--- Note the top-level mainjs.js code does not re-execute
configjs exporting hello
mainjs calling configjs.hello()
hello John
coffee maincs.coffee
maincs starting
maincs exporting name
maincs requiring configcs
configcs starting
configcs requiring maincs
maincs starting # <-- Look, the top-level maincs.coffee code is re-executing
maincs exporting name
maincs requiring configcs
maincs calling configcs.hello()
TypeError: Object #<Object> has no method 'hello'
所以我认为这种行为与 node.js 要求系统模块缓存的工作方式和 transpile-on-the-fly coffeescript 解释器有关。基本上,如果maincs = require "maincs"
导致重新执行模块中的顶级代码maincs
,我们会遇到循环依赖情况,节点将提供导出对象maincs
的未完成副本。configcs
请阅读关于循环依赖的 node.js 文档,它解释了这种行为(至少部分地)。
现在,有趣的是,如果您确保在require之前hello
导出了该函数,则可以部分解决。但是,从代码中引用将不起作用,因为 main 在第一次执行时将是未定义的。maincs
main.name
hello
maincs2.coffee
console.log "maincs2 starting"
console.log "maincs2 exporting name"
module.exports =
name: 'John'
console.log "maincs2 requiring configcs2"
configcs2 = require './configcs2'
console.log "maincs2 calling configcs2.hello()"
configcs2.hello()
configcs2.coffee
console.log "configcs2 starting"
console.log "configcs2 exporting hello"
module.exports =
hello: ->
console.log "hello from configcs"
console.log "configcs2 requiring maincs2"
maincs2 = require "./maincs2"
coffee maincs2.coffee
maincs2 starting
maincs2 exporting name
maincs2 requiring configcs2
configcs2 starting
configcs2 exporting hello
configcs2 requiring maincs2
maincs2 starting
maincs2 exporting name
maincs2 requiring configcs2
maincs2 calling configcs2.hello()
hello from configcs
maincs2 calling configcs2.hello()
hello from configcs
所以基本上,这种行为是模块如何被缓存的组合,它如何require
与循环依赖交互,以及咖啡转译器本身的某些方面。请注意,将 .coffee 转换为 .js 并使用 node 执行可以避免在具有 IIFE 包装器的常规咖啡脚本和没有包装器的裸咖啡脚本中出现此问题,因此 IIFE 包装器似乎无关紧要(无论如何,节点基本上都会添加自己的)。