Как бороться с циклическими зависимостями в узле.js


в последнее время я работаю с 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 113

12 ответов:

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

попробуйте установить свойства на module.exports, вместо того, чтобы заменить его полностью. Например,module.exports.instance = new ClassA() in 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');

я использую этот шаблон все время для доступа экспресс.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 в приведенном выше примере), это также возможно, просто убедитесь, что в файле, который запускает циклическое требование, модуль'.поставляемый. = .. в заявлении произойдет, прежде чем требовать заявление.

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

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;

решение, которое требует минимальных изменений, расширяется 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-модуль, который использует метод 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

Как насчет ленивых требований только тогда, когда вам нужно? Так что ваш 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;

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

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

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

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

The = назначение логически избыточно, так как это просто установка 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() {

}