200

私は最近nodejsを使用していますが、まだモジュールシステムを理解しているので、これが明らかな質問である場合はお詫びします。以下のようなコードが必要です。

a.js(ノードで実行されるメインファイル)

var ClassB = require("./b");

var ClassA = function() {
    this.thing = new ClassB();
    this.property = 5;
}

var a = new ClassA();

module.exports = a;

b.js

var a = require("./a");

var ClassB = function() {
}

ClassB.prototype.doSomethingLater() {
    util.log(a.property);
}

module.exports = ClassB;

私の問題は、ClassBのインスタンス内からClassAのインスタンスにアクセスできないことのようです。

私が望むものを達成するためにモジュールを構造化する正しい/より良い方法はありますか?モジュール間で変数を共有するためのより良い方法はありますか?

4

16 に答える 16

196

module.exports完全に置き換えるのではなく、にプロパティを設定してみてください。例:module.exports.instance = new ClassA()in a.jsmodule.exports.ClassB = ClassBin b.js。循環モジュールの依存関係を作成すると、必要なモジュールはmodule.exports必要なモジュールから不完全なものへの参照を取得します。これは後で他のプロパティを追加できますが、全体を設定するとmodule.exports、実際には必要なモジュールにない新しいオブジェクトが作成されます。アクセスする方法。

于 2012-06-03T18:54:03.337 に答える
106

node.jsは循環require依存を許可しますが、ご存知のように、かなり厄介な場合があり、コードを再構築して不要にする方がよいでしょう。たぶん、必要なことを達成するために他の2つを使用する3番目のクラスを作成します。

于 2012-06-03T18:49:14.413 に答える
60

[編集]2015年ではなく、ほとんどのライブラリ(つまり、エクスプレス)がより良いパターンで更新を行ったため、循環依存関係は不要になりました。単に使用しないことをお勧めします


ここで古い答えを掘り下げていることはわかっています...ここでの問題は、ClassBが必要になった後にmodule.exportsが定義されることです。(JohnnyHKのリンクが示しています)循環依存関係はNodeでうまく機能し、同期的に定義されているだけです。適切に使用すると、実際には多くの一般的なノードの問題を解決します(app他のファイルからexpress.jsにアクセスするなど)

循環依存のファイルが必要になる前に、必要なエクスポートが定義されていることを確認してください。

これは壊れます:

var ClassA = function(){};
var ClassB = require('classB'); //will require ClassA, which has no exports yet

module.exports = ClassA;

これは機能します:

var ClassA = module.exports = function(){};
var ClassB = require('classB');

app他のファイルのexpress.jsにアクセスするために、このパターンを常に使用しています。

var express = require('express');
var app = module.exports = express();
// load in other dependencies, which can now require this file and use app
于 2014-01-21T16:51:10.653 に答える
46

(JohnnyHKがアドバイスするように)3番目のクラスを導入するのは本当に人工的な場合があるため、Ianzzに加えて:module.exportsを置き換えたい場合、たとえば、クラスを作成している場合(b.jsファイルのように上記の例)、これも可能です。循環requireを開始しているファイルで、'module.exports=...'ステートメントがrequireステートメントの前にあることを確認してください。

a.js(ノードで実行されるメインファイル)

var ClassB = require("./b");

var ClassA = function() {
    this.thing = new ClassB();
    this.property = 5;
}

var a = new ClassA();

module.exports = a;

b.js

var ClassB = function() {
}

ClassB.prototype.doSomethingLater() {
    util.log(a.property);
}

module.exports = ClassB;

var a = require("./a"); // <------ this is the only necessary change
于 2012-12-31T06:26:17.087 に答える
14

解決策は、他のコントローラーを要求する前に、エクスポートオブジェクトを「前方宣言」することです。したがって、すべてのモジュールをこのように構成し、そのような問題が発生しない場合は、次のようになります。

// Module exports forward declaration:
module.exports = {

};

// Controllers:
var other_module = require('./other_module');

// Functions:
var foo = function () {

};

// Module exports injects:
module.exports.foo = foo;
于 2014-08-18T00:35:39.300 に答える
9

これは簡単に解決できます。module.exportsを使用するモジュールで他に何かが必要になる前に、データをエクスポートするだけです。

classA.js

class ClassA {

    constructor(){
        ClassB.someMethod();
        ClassB.anotherMethod();
    };

    static someMethod () {
        console.log( 'Class A Doing someMethod' );
    };

    static anotherMethod () {
        console.log( 'Class A Doing anotherMethod' );
    };

};

module.exports = ClassA;
var ClassB = require( "./classB.js" );

let classX = new ClassA();

classB.js

class ClassB {

    constructor(){
        ClassA.someMethod();
        ClassA.anotherMethod();
    };

    static someMethod () {
        console.log( 'Class B Doing someMethod' );
    };

    static anotherMethod () {
        console.log( 'Class A Doing anotherMethod' );
    };

};

module.exports = ClassB;
var ClassA = require( "./classA.js" );

let classX = new ClassB();
于 2018-04-30T09:10:19.713 に答える
8

最小限の変更で済むソリューションはmodule.exports、オーバーライドするのではなく拡張することです。

a.js-メソッドを使用するアプリのエントリポイントとモジュールはb.js*から実行します

_ = require('underscore'); //underscore provides extend() for shallow extend
b = require('./b'); //module `a` uses module `b`
_.extend(module.exports, {
    do: function () {
        console.log('doing a');
    }
});
b.do();//call `b.do()` which in turn will circularly call `a.do()`

b.js-メソッドを使用するモジュールはa.jsから実行します

_ = require('underscore');
a = require('./a');

_.extend(module.exports, {
    do: function(){
        console.log('doing b');
        a.do();//Call `b.do()` from `a.do()` when `a` just initalized 
    }
})

それは機能し、生成されます:

doing b
doing a

このコードは機能しませんが:

a.js

b = require('./b');
module.exports = {
    do: function () {
        console.log('doing a');
    }
};
b.do();

b.js

a = require('./a');
module.exports = {
    do: function () {
        console.log('doing b');
    }
};
a.do();

出力:

node a.js
b.js:7
a.do();
    ^    
TypeError: a.do is not a function
于 2014-06-26T10:31:48.247 に答える
8

あなたがする必要があるときだけ怠惰な要求はどうですか?したがって、b.jsは次のようになります

var ClassB = function() {
}
ClassB.prototype.doSomethingLater() {
    var a = require("./a");    //a.js has finished by now
    util.log(a.property);
}
module.exports = ClassB;

もちろん、すべてのrequireステートメントをファイルの先頭に置くことをお勧めします。しかし、他の点では無関係なモジュールから何かを選んだことを許す場合がありますこれをハックと呼びますが、依存関係を追加したり、モジュールを追加したり、新しい構造(EventEmitterなど)を追加したりするよりも、これが優れている場合があります。

于 2017-04-17T13:36:28.410 に答える
7

重要なことは、与えられたオブジェクトを再割り当てしないことmodule.exportsです。そのオブジェクトは、サイクル内の他のモジュールにすでに与えられている可能性があるためです。内部にプロパティを割り当てるだけでmodule.exports、他のモジュールにそれらが表示されます。

したがって、簡単な解決策は次のとおりです。

module.exports.firstMember = ___;
module.exports.secondMember = ___;

唯一の本当の欠点は、module.exports.何度も繰り返す必要があることです。


lanzzとsetecの回答と同様に、私は次のパターンを使用しています。これは、より宣言的であると感じます。

module.exports = Object.assign(module.exports, {
    firstMember: ___,
    secondMember: ___,
});

すでに他のモジュールに与えられているオブジェクトにObject.assign()メンバーをコピーします。exports

=割り当てはそれ自体に設定されているだけなので論理的に冗長ですmodule.exportsが、IDE(WebStorm)がこのモジュールのプロパティであることを認識するのに役立つため、これを使用していますfirstMember。「移動->宣言」(Cmd-B)および他のツールは他のファイルから機能します。

このパターンはあまりきれいではないので、循環依存の問題を解決する必要がある場合にのみ使用します。

特にES6のプロパティの省略形を使用する場合は、オブジェクトからエクスポートを簡単に追加および削除できるため、これはリビールパターンにかなり適しています。

Object.assign(module.exports, {
    firstMember,
    //secondMember,
});
于 2017-03-03T04:06:22.490 に答える
6

私が見たもう1つの方法は、最初の行でエクスポートして、次のようにローカル変数として保存することです。

let self = module.exports = {};

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

// Exporting the necessary functions
self.func = function() { ... }

私はこの方法を使う傾向があります、あなたはそれの欠点について知っていますか?

于 2017-12-20T17:58:03.800 に答える
4

TL; DR

exports.someMember = someMemberの代わりに使用してmodule.exports = { // new object }ください。

拡張回答

lanzzの回答を読んだ後、私はついにここで何が起こっているのかを理解することができたので、私はこの主題について2セントを与え、彼の回答を拡張します。

この例を見てみましょう:

a.js

console.log("a starting");

console.log("a requires b");
const b = require("./b");
console.log("a gets b =", b);

function functionA() {
  console.log("function a");
}

console.log("a done");
exports.functionA = functionA;

b.js

console.log("b starting");

console.log("b requires a");
const a = require("./a");
console.log("b gets a =", a);

function functionB() {
  console.log("On b, a =", a)
}

console.log("b done");
exports.functionB = functionB;

main.js

const a = require("./a");
const b = require("./b");

b.functionB()

出力

a starting
a requires b
b starting
b requires a
b gets a = {}
b done
a gets b = { functionB: [Function: functionB] }
a done
On b, a = { functionA: [Function: functionA] }

ここでは、最初bは空のオブジェクトをとして受け取り、a次にa完全にロードされると、その参照は。を介して更新されることがわかりますexports.functionA = functionA。代わりに、を介してモジュール全体を別のオブジェクトに置き換えるとmodule.exports、新しいオブジェクトを指すのではなく、最初から同じ空のオブジェクトを指すため、からbの参照が失われます。a

したがって、次のaようにエクスポートすると、次のようmodule.exports = { functionA: functionA }に出力されます。

a starting
a requires b
b starting
b requires a
b gets a = {}
b done
a gets b = { functionB: [Function: functionB] }
a done
On b, a = {} // same empty object
于 2021-04-01T20:15:17.363 に答える
3

実際、私は依存関係を要求することになりました

 var a = null;
 process.nextTick(()=>a=require("./a")); //Circular reference!

きれいではありませんが、動作します。b.jsを変更するよりも理解しやすく正直です(たとえば、modules.exportを拡張するだけです)。それ以外の場合はそのままで完璧です。

于 2017-04-17T14:01:40.593 に答える
3

これは、完全に使用できることがわかった簡単な回避策です。

ファイル'a.js'

let B;
class A{
  constructor(){
    process.nextTick(()=>{
      B = require('./b')
    })
  } 
}
module.exports = new A();

ファイル'b.js'に次のように記述します

let A;
class B{
  constructor(){
    process.nextTick(()=>{
      A = require('./a')
    })
  } 
}
module.exports = new B();

このようにして、イベントループクラスの次の反復で正しく定義され、それらのrequireステートメントは期待どおりに機能します。

于 2019-05-24T03:00:34.307 に答える
1

これを回避する1つの方法は、あるファイルを他のファイルに必要とせず、別のファイルで必要なものを関数の引数として渡すことです。このようにして、循環依存が発生することはありません。

于 2020-04-25T14:42:35.677 に答える
1

多くの場合、非常に単純な解決策は次のとおりです。

通常、ファイルの先頭にrequireがあります...

var script = require('./script')
function stuff() {
      script.farfunction()
}

代わりに、「関数内」でそれを要求するだけです

function stuff() {
      var _script = require('./script')
      _script.farfunction()
}
于 2022-01-17T12:35:04.540 に答える
-4

循環依存関係を排除できない場合(例:useraccount <---> userlogin)、もう1つのオプションがあります...

使用するのと同じくらい簡単ですsetTimeout()

//useraccount.js

let UserLogin = {};

setTimeout(()=>UserLogin=require('./userlogin.js'), 10);

class UserAccount{
 
getLogin(){
return new UserLogin(this.email);

}

}



//userlogin.js

let UserAccount ={};

setTimeout(()=>UserAccount=require('./useraccount.js'), 15);


class UserLogin{

getUser(){

return new User(this.token);

}

}
于 2020-12-16T00:47:11.513 に答える