Как обращаться с циклическими зависимостями в Node.Яш

в последнее время я работаю с nodejs и все еще пытаюсь справиться с системой модулей, поэтому извиняюсь, если это очевидный вопрос. Я хочу код примерно следующим образом:

а.Яш (основной файл запускается с узлом)

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

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

var a = new ClassA();

module.exports = a;

б.Яш

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

var ClassB = function() {
}

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

module.exports = ClassB;

моя проблема заключается в том, что я не могу получить доступ к экземпляру ClassA из экземпляра ClassB.

есть ли правильный / лучший способ структурирования модули для достижения того, чего я хочу? Есть ли лучший способ обмена переменными между модулями?

12 ответов


в то время как узел.js позволяет circular require зависимости, как вы обнаружили, это может быть довольно сумбурно и вам, вероятно, лучше реструктурировать свой код, чтобы он не нужен. Возможно, создайте третий класс, который использует два других, чтобы выполнить то, что вам нужно.


попробуйте установить свойства на module.exports, вместо того, чтобы полностью заменить его. Е. Г., module.exports.instance = new ClassA() на a.js, module.exports.ClassB = ClassB на b.js. Когда вы делаете циклические зависимости модулей, требующий модуль получит ссылку на неполный module.exports из необходимого модуля, в который вы можете добавить другие свойства последнего, но когда вы установите весь module.exports, вы фактически создаете новый объект, к которому у требующего модуля нет доступа.


[EDIT] это не 2015, и большинство библиотек (например, express) сделали обновления с лучшими шаблонами, поэтому циклические зависимости больше не нужны. Я рекомендую просто не используя их.


Я знаю, что нахожу здесь старый ответ... Проблема здесь в этом модуле.экспорт определяется после вам требуется ClassB. (что показывает ссылка JohnnyHK) Циклические зависимости отлично работают в узле, они просто определяются синхронно. При использовании правильно, они фактически решают много общих проблем узла (например, доступ к express.js app из других файлов)

просто убедитесь, что ваш необходимый экспорт определен до требуется файл с круговой зависимостью.

это сломает:

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');

я использую этот шаблон все время для доступа к express.js app в другие файлы:

var express = require('express');
var app = module.exports = express();
// load in other dependencies, which can now require this file and use app

иногда действительно искусственно вводить третий класс (как советует Джонник), поэтому в дополнение к Ianzz: Если вы хотите заменить модуль.экспорт, например, если вы создаете класс (например, b.JS-файл в приведенном выше примере), это также возможно, просто убедитесь, что в файле, который запускает циркуляр require, 'module.поставляемый. = ..- утверждение происходит до утверждения require.

а.Яш (основной файл работает с узел)

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

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

var a = new ClassA();

module.exports = a;

б.Яш

var ClassB = function() {
}

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

module.exports = ClassB;

var a = require("./a"); // <------ this is the only necessary change

решение состоит в том, чтобы "вперед объявить" ваш объект экспорта, прежде чем требовать любого другого контроллера. Поэтому, если вы структурируете все свои модули так, и вы не столкнетесь с такими проблемами:

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

};

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

// Functions:
var foo = function () {

};

// Module exports injects:
module.exports.foo = foo;

решение, которое требует минимальных изменений, расширяется переопределив его.

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-модуль, который использует метод do из 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

пока этот код не будет работа:

а.Яш

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

б.Яш

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

Как насчет ленивых требований только тогда, когда вам нужно? Так что твоя Би.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 поверх файла. Но нет!--4-- > are случаи, когда я прощаю себя за то, что выбрал что-то из другого несвязанного модуля. Назовите это взломом, но иногда это лучше, чем введение дополнительной зависимости или добавление дополнительного модуля или добавление новых структур (EventEmitter и т. д.)


другой метод, который я видел, люди делают, экспортирует в первой строке и сохраняет его как локальную переменную, например:

let self = module.exports = {};

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

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

Я предпочитаю использовать этот метод, вы знаете о каких-либо недостатки?


на самом деле мне потребовалась моя зависимость с

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

не очень, но это работает. Это более понятно и честно, чем изменение b.js (например, только увеличение модулей.export), который в противном случае совершенен как есть.


подобно ответам lanzz и setect, я использую следующий шаблон:

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

на Object.assign() копирует членов в exports объект, который уже был передан другим модулям.

на = назначение логически избыточно, так как оно просто устанавливает module.exports для себя, но я использую его, потому что он помогает моей IDE (WebStorm) распознать это firstMember является свойством этого модуля, поэтому " перейти к - > объявление "(Cmd-B) и другие инструменты будут работа из других файлов.

этот шаблон не очень красивый, поэтому я использую его только тогда, когда необходимо решить проблему циклической зависимости.


вы можете легко решить эту проблему: просто экспортируйте свои данные, прежде чем вам понадобится что-либо еще в модулях, где вы используете модуль.экспорт:

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();

для вашей проблемы, вы можете использовать объявления функций.

class-b.js:

var ClassA = require('./class-a')

module.exports = ClassB

function ClassB() {

}

class-a.js:

var classB = require('./class-b')

module.exports = ClassA

function ClassA() {

}