妙な挙動をするなぁ(;´Д`) と思って調べてみたら、 RequireJS では define の引数で指定したコードは一度しか読み込まれないらしい。

読み込んだものは、グローバルなオブジェクトで管理されて同じものは読み込まれないようになっている。

require.jsの、requireとdefineの違い - webネタ

サンプルコードを書いて試してみる。

RequireJS を使って JavaScript のコードを読み込むには、メイン処理を書いた JavaScript ファイルのパスを、 script 要素の data-main 属性に指定する。

index.html


<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>RequireJS: sample</title>
  <!-- data-main attribute tells require.js to load
       scripts/main.js after require.js loads. -->
  <script data-main="scripts/main" src="scripts/require.js"></script>
</head>
<body>
<h1>RequireJS: sample</h2>
<p>Look at: console.log</p>
<p>Use: <a href="http://requirejs.org/">RequireJS</a></p>
</body>
</html>

グローバル変数的なものを生み出してみる

今回のファイル構成。


├── index.html
└── scripts
    ├── main.js
    ├── require.js
    ├── foo.js
    ├── bar.js
    └── counter
        └── first.js

数値カウント機能を持つ FirstCounter クラス的なものを作る。

scripts/counter/first.js


define(function() {

  console.log("hello, first.js");

  var FirstCounter = function(){

    this.count = 0;

    this.countup = function(){
      this.count++;
      return this.count;
    };
  };

  return new FirstCounter(); // new したオブジェクトを返す
});

foo.js から FirstCounter を読み込んでカウントアップ。

scripts/foo.js


define(["counter/first"], function(c) {

  console.log("hello, foo.js");

  var _invoke = function() {
    console.log("foo:" + c.countup());
  }

  return {invoke: _invoke};
});

bar.js からも FirstCounter を読み込んでカウントアップ。

scripts/bar.js


define(["counter/first"], function(c) {

  console.log("hello, bar.js");

  var _invoke = function() {
    console.log("bar:" + c.countup());
  }

  return {invoke: _invoke};
});

メインとなる main.js から foo.js と bar.js を使う。 foo.js と bar.js には繋がりは無い。

scripts/main.js


requirejs(
  ["foo", "bar"],
  function(f, b) {

  console.log("hello, main.js");

  // global counter
  console.log("FirstCounter test");
  f.invoke();
  b.invoke();
  f.invoke();
  b.invoke();
});

console.log への出力結果。*.js ファイルはそれぞれ一度しか読み込まれない。


hello, first.js
hello, foo.js
hello, bar.js
hello, main.js
FirstCounter test
foo:1
bar:2
foo:3
bar:4

foo.js と bar.js が使っている FirstCounter は同じオブジェクトになっているため、カウントは 1,2,3,4 となる。

define 関数の第2引数に指定した関数で返したオブジェクトは、 RequireJS の中でのグローバルなオブジェクトとして管理されてしまう。そのため、状態を持つオブジェクトを return するのはあまり行儀の良い実装ではない。グローバル変数のような挙動になってしまう。

グローバル変数的にしないでやってみる

サンプルコードを書き直す。今回のファイル構成。


├── index.html
└── scripts
    ├── main.js
    ├── require.js
    ├── foo2.js
    ├── bar2.js
    └── counter
        └── second.js

もう少し安全な数値カウンター SecondCounter を実装してみる。

scripts/counter/second.js


define(function() {

  console.log("hello, second.js");

  var SecondCounter = function(){

    this.count = 0;

    this.countup = function(){
      this.count++;
      return this.count;
    };
  };

  return SecondCounter; // new しない
});

foo2.js から SecondCounter を読み込んでカウントアップ。

scripts/foo2.js


define(["counter/second"], function(c) {

  console.log("hello, foo2.js");

  var counter = new c();

  var _invoke = function() {
    console.log("foo2:" + counter.countup());
  }

  return {invoke: _invoke};
});

bar2.js からも SecondCounter を読み込んでカウントアップ。

scripts/bar2.js


define(["counter/second"], function(c) {

  console.log("hello, bar2.js");

  var counter = new c();

  var _invoke = function() {
    console.log("bar2:" + counter.countup());
  }

  return {invoke: _invoke};
});

メインとなる main.js から foo2.js と bar2.js を使う。 foo2.js と bar2.js には繋がりは無い。

scripts/main.js


requirejs(
  ["foo2", "bar2"],
  function(f2, b2) {

  console.log("hello, main.js");

  // global counter
  console.log("FirstCounter test");
  f.invoke();
  b.invoke();
  f.invoke();
  b.invoke();
});

console.log への出力結果。


hello, second.js
hello, foo2.js
hello, bar2.js
hello, main.js
SecondCounter test
foo2:1
bar2:1
foo2:2
bar2:2

foo2.js と bar2.js が使っている SecondCounter の定義オブジェクトは同じだが、それぞれ SecondCounter を new して生成しているため、別々のオブジェクトとして存在している。

RequireJS を使うときは、グローバル変数的なものになるように書くか、ならないように書くか、意識して書きたい。

今回のサンプルコード ⇒ RequireJS: sample

tags: javascript

Posted by NI-Lab. (@nilab)