coffee require()语句导致循环引用?

时间:2013-01-10 13:37:52

标签: node.js coffeescript

我有一个奇怪的问题,Coffeescript没有正确地缓存/完成加载以循环方式引用的require()语句。

当我使用 node main.js ...

运行此代码时

main.js

module.exports = {
    name: 'John'
}

var config = require('./config');
config.hello();

config.js

var main = require('./main');

module.exports = {
    hello: function() {
        console.log("Hello " + main.name);
    }
}

...我得到以下输出:

  

你好约翰

然而,当我用 coffee main.coffee 运行等效的coffeescript代码时...

main.coffee

module.exports =
    name: 'John'

config = require './config'
config.hello()

config.coffee

main = require './main'

module.exports =
    hello: ->
        console.log "hello #{main.name}"

......我明白了:

  

TypeError:Object#没有方法'hello'

当我将代码编译为普通的'Javascript并通过节点运行时,没关系。

咖啡发生了什么?

1 个答案:

答案 0 :(得分:4)

我的理论是这种行为是node.js require缓存如何工作,节点如何处理周期性依赖关系,以及关于将.coffee文件直接加载到节点时咖啡转换器如何工作的组合

我使用说明性日志记录制作了不同的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如何需要系统模块缓存工作以及运行中的coffeescript解释器有关。基本上,如果maincs = require "maincs"导致重新执行maincs模块中的顶级代码,我们就会遇到循环依赖情况,其中节点将提供maincs未完成的副本configcs导出对象。

请阅读node.js documentation on cyclic dependencies that explains this behavior(至少部分)。

现在,有趣的是,如果您确保{<1}}函数在之前导出,您需要hello,那么您可以部分地避免这种情况。但是,从maincs代码中引用main.name将不起作用,因为在首次执行时将不会定义main。

<强> maincs2.coffee

hello

<强> configcs2.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()

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 缓存模块的方式,与周期性依赖关系的交互方式以及咖啡转换器本身的某些方面的组合。请注意,将.coffee转换为.js并使用节点执行可以避免在使用IIFE包装器的常规coffeescript和没有包装器的裸coffeescript时出现此问题,因此IIFE包装器似乎并不重要(节点基本上无论如何都添加了它自己)。