奇怪的node.js运行时错误:XXX不是一个函数(但我的确将XXX定义为一个函数〜!)

我发现了一个特殊的JavaScript编码组合/序列,会导致奇怪的node.js运行时错误(“XXX不是一个函数”,但我的确将XXX定义为函数!)。 它不依赖于任何其他库和工具,但一些简单的打字脚本/ JavaScript代码可能会导致此错误。 我不明白为什么我得到这样的运行时错误,有谁能帮助我? 如果它对应一些node.js已知的bug,请告诉我相应的票号,以便我可以更多地了解这个。 谢谢〜!

(我已经全部升级到最新版本:node.js v4.1.1,Visual Studio 2015社区版本,VS 2015 node.js工具1.1.30716.01)

事实上,我正在使用Visual Studio 2015社区版本来编写一些打印脚本代码( .ts),并自动获取由VS生成的JavaScript代码( .js)。 针对这种特殊的编码顺序,Visual Studio 2015可以高兴地接受它,并生成相应的javascript代码。 但是,当node.js运行这些javascript代码时,node.js会产生奇怪的运行时错误。 因此,我认为这种行为只与node.js完全相关,而不是打字稿。

总之,VS2015中的typescript模块认为这个组合是一个有效的typescript / javascript序列,但是生成的javascript文件不能被node.js执行,如果我改变了一些语句的顺序,它就可以工作〜! 真奇怪。

打字稿/ javascript编码组合/序列包含8个简单文件。 我花了几天的时间来缩小问题的范围,发现组合是最简单的forms,无法进一步简化以获得相同的运行时错误。 如果我进一步删除一些部分,运行时错误将会消失。 当执行“main.js”文件时,node.js会产生下面的错误,但我不明白为什么。 A_function1()和A_function2()是2个简单的函数(你可以在下面看到完整的文件内容),node.js可以成功的find并执行A_function1(),而不是A_function2()。

c:\>node --version v4.1.1 c:\>node main.js c:\c.js:3 a.A_function2(); ^ TypeError: a.A_function2 is not a function at Object.C_function (c:\c.js:3:7) at Object.<anonymous> (c:\main.js:4:3) at Module._compile (module.js:434:26) at Object.Module._extensions..js (module.js:452:10) at Module.load (module.js:355:32) at Function.Module._load (module.js:310:12) at Module.runMain [as _onTimeout] (module.js:475:10) at Timer.listOnTimeout (timers.js:89:15) Press any key to continue... 

在这里输入图像说明

================================================== ======================= 8个打字稿文件如下:

main.ts

 import d = require("./d"); // --> If I change the order of these "imports" to "c --> d --> e", the runtime error would disappear. import e = require("./e"); import c = require("./c"); c.C_function(); d; e; console.log("safe!"); // --> If the execution reaches this line, every thing would be fine. If that runtime error happened, you could not see this "safe!" 

a.ts

 import g = require("./g"); class A { static A_function1() { } static A_function2() { var tmp = g.G_enum.G_enum_value_1; } } export = A; 

b.ts:

 import a = require("./a"); export function B_function() { a.A_function1(); // --> This line would not cause any runtime error, node.js could successfully find what A_function1() is, and execute it~! } 

c.ts:

 import a = require("./a"); export function C_function() { a.A_function2(); // --> This line causes the runtime error, but A_function2() is really a function~! } 

d.ts:

 import f = require("./f"); f.F_function; 

e.ts:

 import g = require("./g"); g.G_function; 

f.ts:

 import b = require("./b"); export function F_function() { b.B_function(); } 

g.ts:

 import c = require("./c"); export enum G_enum { G_enum_value_1 } export function G_function() { c.C_function(); } 

================================================== ======================= VS2015生成的相应javascript文件如下:

main.js:

 var d = require("./d"); var e = require("./e"); var c = require("./c"); c.C_function(); d; e; console.log("safe!"); 

a.js:

 var g = require("./g"); var A = (function () { function A() { } A.A_function1 = function () { }; A.A_function2 = function () { var tmp = g.G_enum.G_enum_value_1; }; return A; })(); module.exports = A; 

b.js:

 var a = require("./a"); function B_function() { a.A_function1(); } exports.B_function = B_function; 

c.js:

 var a = require("./a"); function C_function() { a.A_function2(); } exports.C_function = C_function; 

d.js:

 var f = require("./f"); f.F_function; 

e.js:

 var g = require("./g"); g.G_function; 

f.js:

 var b = require("./b"); function F_function() { b.B_function(); } exports.F_function = F_function; 

g.js:

 var c = require("./c"); (function (G_enum) { G_enum[G_enum["G_enum_value_1"] = 0] = "G_enum_value_1"; })(exports.G_enum || (exports.G_enum = {})); var G_enum = exports.G_enum; function G_function() { c.C_function(); } exports.G_function = G_function; 

basarat的答案是正确的,因为你有一个循环引用,但是这个答案对于你的问题并不是真的完整的(尽pipe公平地说,你所问的实际上并不是很特别;循环模块依赖在某些情况下是相当普遍的代码结构)。

在你的代码中,模块a的输出与其他模块不同。 在模块a ,为模块导出一个不同的值( export = A ),而其他模块只是将函数添加到其默认导出对象( export function C_function )。 模块a尝试导出不同值的事实是问题的关键。

当Node.js中出现循环模块依赖关系时,为了打破循环,即使您尝试指定明确的导出值,Node.js也必须将冲突模块的导出值定义为其创build的默认导出对象。 对于只是将属性添加到默认导出对象( export functionexport var export function )的模块,这不是问题。 但是,当您尝试定义不同的导出对象(使用export = )时,该对象最终将被丢弃,以支持默认的导出对象。

换句话说, export = A会在代码中被忽略,因为这个模块最终会成为像这样的一个循环的一部分:

主 – > d – > f – > b – > a – > g – > c – > a

当第二次看到a时,运行时没有其他select,只能给c一个默认的导出对象,否则它不能parsing依赖关系图。

要修复你的调用,你需要重构你的代码来移除循环依赖,或者你需要导出附加到默认导出对象的函数和variables,而不是试图导出你自己创build的不同对象( class A )。

循环引用

如果您有循环引用: https : //nodejs.org/api/modules.html#modules_cycles ,则可能返回一个对象,该对象现在可能没有与模块导入关联的function。

https://github.com/TypeStrong/atom-typescript#dependency-view去,它会发现你可能有任何循环引用&#x1F339;