javascriptの循環参照、circular dependencies。TypeError

stokpic / Pixabay
javascriptを書いていて、requireの書き方で、ちょっとハマったのでメモ。
次のような要件を実装していくとします。
- 子要素を生成することができるclass Person{}をつくる。
- 要素を生成する機能は、class外に別途factory()メソッドを用意する。
- Personにはchild()関数があり、factory()を実行して、子要素を作れる。
この仕組みを1ファイルですべて記述すると成功するのだけど、
ファイルを分割して、双方の関数をお互いがrequireしあうようにすると、
書き方によっては、そんな関数無いですよ!?と怒られます。
TypeError: factory is not a function
では。そのコードを紹介します。
動かないコード
factory.js
const {Person} = require("./Person");
function factory(){
return new Person();
}
module.exports = {
factory
};
Person.js
const {factory} = require("./factory");
class Person{
constructor() {}
say(){
return "hello";
}
child(){
return factory(); // ←このfactory()がundefinedだと怒られます。
}
}
module.exports = {
Person
};
const {factory} = require("./factory");
const parent = factory()
parent.say() // "hello"
parent.child().say() // "helloとでてほしい"
parent.child().child().say() // "helloとでてほしい"
Circular dependencies
なぜエラーになるかという所なのだけど、
- requireを記述した1行目で、require先のファイルが読み込まれる。
- require先のファイルが読み込み終わるまでは、require元のファイルの2行目以降が評価されない。
- で、require先の1行目でも、require元のファイルをrequireしているので、
- requireの無限ループされてしまう。
このことを、循環参照だったり、circular dependenciesだったり呼ばれるみたい。
では、どのようにするか。まずは、訂正版のコード。
Person.js 訂正版
class Person{
constructor() {}
say(){
return "hello";
}
child(){
const {factory} = require("./factory");
return factory() // ←このfactory()がundefinedだと怒られます。
}
}
module.exports = {
Person
};
違いは、1行目に書いていた、require()を、実際に使う直前(childメソッド内)に移動させています。
「requireはファイルの最初にしか記述してはいけない」と思い込んでいたのだけど、
そんなことはないみたいで、実行したい直前にrequireを使い評価させることで、循環参照を回避させることができるみたい。
今回紹介した以外にも、解決方法あるみたいで、こちらの記事に、かなり具体的に解説されていましたので、合わせて参照されることをおすすめします!
node.jsにおける循環参照に対処するための3つの方法 – Qiita