Как обращаться с циклическими зависимостями в 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() {
}