2017-02-07 35 views
1

请看下面的例子:为什么我的导出函数不是函数?

// bar.js 
const foo = require('./foo');  

module.exports = function(args){ 
    let f = foo(args) 
} 
// foo is not a function 

然后:

// bar.js 
module.exports = function(args){ 
    let f = require('./foo')(args) 
} 
// behaves as expected 

foo.js样子:

const bar = require('./bar'); 
module.exports = function(args){ //same args as bar.js 
    const foo = {}; 
    foo.f1 = function(arg){ 
     console.log("Hi") 
    } 

    return foo 
}; 
+3

'foo'的代码肯定不会是**不必要的代码。 – Pointy

+0

我用更多的代码更新了这个问题 – znat

+0

@znat是的,这就是我的预期,看到我的答案解释和解决方案。 –

回答

1

你正在处理循环依赖的问题。我想foo.js看起来在某种程度上是这样的:

const bar = require('./bar'); 

module.exports = function(args) { 
    console.log(args); 
}; 

让我们跟随虚拟机执行,以执行代码的步骤(假设您加载foo.js在前):

  1. 负载foo.js。用A = {}初始化出口。
  2. 加载bar.js。用B = {}初始化出口。现在内bar.js
    1. bar.js需要foo.js出口(目前仍在A = {}),所以在bar.js设置fooA = {}(这就是问题所在!)。
    2. 用声明的函数替换bar.js的输出。
  3. 用声明的函数替换foo.js的输出。

如您所见,如果您使用循环依赖关系,虚拟机无法跟踪bar.jsfoo.js的导出。

在第二个示例中,稍后在调用函数时解决导出foo.js。那时候,foo.js的出口已经被该功能取代了,它就起作用了。


简版:这是发生了什么事。

  1. 虚拟机负载foo.js并初始化的foo.js与对象A出口。
  2. VM加载bar.js,并用对象B初始化bar.js的导出。
  3. VM将bar.js中的变量foo设置为出口foo.js,因此为foo = A
  4. 的VM取代的bar.js出口与所述功能C
  5. 的VM设置在foo.js到的bar.js出口可变bar,因此bar = C
  6. VM用功能D取代foo.js的输出。

正如你可以再次看到,在bar.js变量foo仍然A,即使它应该是D


你通常会想忌用循环依赖。如果确有必要,那就不要更换module.exports,而是附加属性的对象:

module.exports.doBarAction = function(args){ 
    let f = foo.doFooAction(args) 
}; 

这解决了问题,因为对象仍然在运行时是相同的。
但是,在大多数情况下,至少在我们谈论CommonJS模块时,最好摆脱循环依赖关系。

+0

这对于Java程序员来说很难理解:) – znat

+0

@znat是的,Java执行一个静态编译,允许编译器毫无问题地解决循环依赖问题,但是JavaScript虚拟机没有。不要试图将require()与'import'进行比较。 –

相关问题