为什么导出的函数不是函数?

时间:2017-02-07 15:30:56

标签: javascript node.js

考虑以下示例:

// 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
};

1 个答案:

答案 0 :(得分:1)

您正在处理循环依赖的问题。我猜foo.js看起来像这样:

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

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

让我们按照VM执行代码执行的步骤(假设您先加载foo.js):

  1. 加载foo.js。使用A = {}初始化导出。
  2. 加载bar.js。使用B = {}初始化导出。现在在bar.js内:
    1. bar.js要求导出foo.js(仍为A = {}),因此请将foo中的bar.js设置为A = {}(这是问题!)。
    2. bar.js的导出替换为声明的函数。
  3. foo.js的导出替换为声明的函数。
  4. 如您所见,如果您使用循环依赖关系,则VM无法跟踪foo.jsbar.js的导出。

    在第二个示例中,稍后在调用函数时解析foo.js的导出。此时,foo.js的导出已经被函数替换,并且可以正常工作。

    简短版本:这就是发生的事情。

    1. 虚拟机加载foo.js并使用对象foo.js初始化A的导出。
    2. 虚拟机加载bar.js并使用对象bar.js初始化B的导出。
    3. VM将foo中的变量bar.js设置为导出foo.js,因此foo = A
    4. VM使用函数bar.js
    5. 替换C的导出
    6. VM将bar中的变量foo.js设置为导出bar.js,因此bar = C
    7. VM使用函数foo.js替换D的导出。
    8. 正如您所看到的那样,foo中的变量bar.js仍为A,即使它应为D

      您通常希望避免使用循环依赖。如果确实有必要,那么不要替换module.exports,而是将属性附加到对象:

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

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