2017-02-07 67 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'進行比較。 –

相關問題