Как бороться с циклическими зависимостями в узле.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 ответов:
в то время как узел.в JS не допускает круговой
require
зависимости, как вы нашли это может быть довольно сумбурно и вам, вероятно, лучше реструктурировать свой код, чтобы он не нужен. Может быть, создать третий класс, который использует два других для выполнения того, что вам нужно.
попробуйте установить свойства на
module.exports
, вместо того, чтобы заменить его полностью. Например,module.exports.instance = new ClassA()
ina.js
,module.exports.ClassB = ClassB
наb.js
. Когда вы делаете циклические зависимости модулей, требующий модуль получит ссылку на неполныйmodule.exports
из необходимого модуля, который вы можете добавить другие свойства последнего, но когда вы установите весьmodule.exports
, вы фактически создаете новый объект, к которому требуемый модуль не имеет доступа.
[EDIT] это не 2015 год, и большинство библиотек (т. е. express) сделали обновления с лучшими шаблонами, поэтому циклические зависимости больше не нужны. Я рекомендую просто не используя их.
Я знаю, что выкапываю здесь старый ответ... Проблема здесь в том, что модуль.экспорт определяется после вам требуется ClassB. (что показывает ссылка JohnnyHK) Циклические зависимости отлично работают в узле, они просто определяются синхронно. При использовании правильно, они на самом деле решают много общих проблем узла (например, доступ к express.jsapp
из других файлов)просто убедитесь, что ваш необходимый экспорт определен до требуется файл с циклической зависимости.
это сломает:
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();