javascriptの循環参照、circular dependencies。TypeError
.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