JavaScriptでもモジュール化したプログラミングが定着してきましたが、モジュールの読み込みの際には副作用が発生することに気をつけないといけません。
副作用とは
「副作用」といっても、医薬品の場合は何がメインの作用で何が副作用かは相対的なものですが(風邪薬の「眠くなる」副作用をメインとした睡眠改善薬のように、状況によって何を主たる作用と考えるかは変わります)、プログラミング用語としては、「副作用」の意味は一定しています。
ある処理が、引数と返り値以外に及ぼす影響
具体的には、以下の様なものがあります。
- 処理の実行を越えて残る変数への代入、破壊的変更
- 入出力、通信など、プログラム外部とのやり取り
例えば、「alert()
でメッセージを表示させる」ようなことも、通常それを目的に行いますが、これも分類は副作用です。
require()
の動作と、副作用
Webpackなどでコンパイルするとわかりやすいですが、個々のファイル(モジュール)は、require
やmodule
などを引数として渡される関数、という形に変換されます。そして、require()
を呼んだ場合には、以下のような処理が走ります。
- モジュールリストから、当該のモジュールが用意されているかをチェックする。
-
用意されていない場合、モジュール定義をラップした関数を呼び出して、
module.exports
などの値を取得する - エクスポートされた値を返す
ということで、以下のようなことがわかります。
- モジュールに書いた、
exports
以外のコードが実行されるのは最大1回(次からはキャッシュされる) - モジュールごとの実行順は呼んだ順
どんなコードを書く?
もちろん、module.exports =
以下の一文で済ませてしまう場合であれば副作用が発生する余地もありませんし、逆に「require
しただけでalert()
を吐く」ようなモジュールも、実用的ではないでしょう。ただ、現実には副作用として何か実行することも十分ありえます。
- 他のモジュールの
require
- イベントの設定
- グローバルや他のモジュールによる変数の、破壊的な変更
- コンポーネント系フレームワークの、コンポーネントの登録
- jQueryプラグインの設定1
副作用をしっかり考えないといけない場合
これらの副作用の中で、比較的影響しうるのが、「イベント設定」です。状況によっては、イベント設定に際して「順番」を考えないといけない場面があります。
たとえば、path/too/foo/first
をpath/too/foo/second
より先にセットしないとうまく動かない、となったとします。そんな場合には、「とりあえずrequire
して読み捨て」で乗り切ることができます。
// firstの副作用が先に動くことを保証する
require('./first');
document.addEventListenr(....);
import
/export
import
やexport
は何かしらの宣言文のようにも見えますが、「JavaScriptを実行して特定の値を取得する」という基本は、require
/exports
の組と同じです。ということで、副作用についても同様に考えないといけません。
-
もっとも、セットしたプラグインが
require()
の結果にならないことを考えると、相性の悪さは否めません。 ↩