後付けでメソッドチェーン - 魔法とスーパールイージ

現在リリース済みのuupaa.jsには、jQueryのようなメソッドチェーンの仕組みがありません。
メソッドチェーンは、各メソッドがthisを返すことでメソッドをつなげ、コード量を圧縮する手法です。
# uupaa.js でも、ここで紹介した後付のメソッドチェーンI/Fを提供する予定です。しない予定です。考え直しました。

永遠の2番手, ミドリの人気者。

以下の HTML + JavaScript なコード見てください。
このLUIGIはメソッドチェーンとは無縁の存在。従来通りにちまちまとメソッドを並べる必要があります。

<html><body><p id="screen"></p><script>
onload = function() {
  var e = document.getElementById("screen");

  function MARIO() {};
  MARIO.prototype = {
    rest: 3,
    get: function(name) { return this[name]; },
    set: function(name, value) { this[name] = value; },
    player1: function(name) { e.innerHTML += "<br />" + name + " x " + this.get("rest"); },
    player2: function(name) { e.innerHTML += "<br />" + name + " x " + this.get("rest"); }
  };

  var LUIGI = new MARIO();
  LUIGI.player1("PEACH");
  LUIGI.set("rest", 1);    // チマ
  LUIGI.player2("YOSHI");  // チマ
}
</script></body></html>

実行結果

PEACH x 3
YOSHI x 1

でもね

でも、LUIGIは本当はこう書きたいんです。アニキを超える人気者になりたいんです。

var LUIGI = new MARIO();
LUIGI.player1("PEACH").set("rest", 1).player2("YOSHI");

魔法をかけてみる

  // --- ここから追加 ---
  (function magic(obj) {
    function chain(func) {
      return function() {
        return func.apply(this, arguments) || this;
         // このthisは、実行時に評価されるため
         // return func.apply(SUPER_LUIGI, arguments) || SUPER_LUIGI; として評価される
      }
    }
    for (var p in { get: 0, set: 0, player1: 0, player2: 0 }) {
      obj.prototype[p] = chain(obj.prototype[p]); // 無名functionで各メソッドをラップ。
    }
  })(MARIO);
  // --- ここまで追加 ---

この短いコードを追加すると…

var SUPER_LUIGI = new MARIO();
SUPER_LUIGI.player1("PEACH").set("rest", 1).player2("YOSHI");

不可能なハズのメソッドチェーンがバリバリに使える。スーパールイージの誕生です!

あとね。

忘れがちですが、returnで結果を返すようなセッターは、魔法をかけてもチェーンできないのでご注意。

  function MARIO() {};
  MARIO.prototype = {
    /* 元のコード。returnしていないのでチェーンできる
    set: function(name, value) { this[name] = value; },
     */
    // returnで値を返しているので、チェーンできない。
    set: function(name, value) { return this[name] = value; },
  };

  SUPER_LUIGI.func1("PEACH").set("rest", 1).func2("YOSHI"); // eval("1.func2()") と評価されエラー

当然ですね。

ついでにワリオ

getter や setter を持たないクラスで、メソッドチェーンをしたいならこうすると良い感じ。

  function WARIO() {};
  WARIO.prototype = {
    rest: 3,
    player1: function(name) { e.innerHTML += "<br />" + name + " x " + this.rest; },
    player2: function(name) { e.innerHTML += "<br />" + name + " x " + this.rest; }
  };
  (function magic(obj) {
    obj.prototype.get = function(prop)        { return this[prop];  }; // ゲッター追加
    obj.prototype.set = function(prop, value) { this[prop] = value; }; // セッター追加
    function chain(func) { ... }
    for (var p in { get: 0, set: 0, ... }) { ... }
  })(WARIO);

  // スーパーワリオ誕生
  var SUPER_WARIO = new WARIO();
  SUPER_WARIO.set("rest", 999).player1("WARIO"); // ガハハハッ

全体のコード

<html><body><p id="screen"></p><script>
onload = function() {
  var e = document.getElementById("screen");

  function MARIO() {};
  MARIO.prototype = {
    rest: 3,
    get: function(name) { return this[name]; },
    set: function(name, value) { this[name] = value; },
    player1: function(name) { e.innerHTML += "<br />" + name + " x " + this.get("rest"); },
    player2: function(name) { e.innerHTML += "<br />" + name + " x " + this.get("rest"); }
  };

  // --- ここから追加 ---
  (function magic(obj) {
    function chain(func) {
      return function() {
         // thisは、実行時のthisなので,
         // return func.apply(SUPER_LUIGI, arguments) || SUPER_LUIGI;  となる
        return func.apply(this, arguments) || this;
      }
    }
    for (var p in { get: 0, set: 0, player1: 0, player2: 0 }) {
      obj.prototype[p] = chain(obj.prototype[p]); // 無名functionで各メソッドをラップ。
    }
  })(MARIO);
  // --- ここまで追加 ---

  // メソッドチェーンが使える。スーパールイージ誕生!
  var SUPER_LUIGI = new MARIO();
  SUPER_LUIGI.player1("PEACH").set("rest", 1).player2("YOSHI");
}
</script></body></html>

反省会

  • メソッドチェーンはいつ頃から?
    • 最初に見たのはテンプレートのコードの中。ざっくり10年前。VC++6.0のSTL::mapがbuggyで…
      • 初めてソレを見たときは「こんなエレガントな書き方が…」と感動した。
  • コンパイラ言語で毎回thisを返すのって、実はコストとのトレードオフなんだよね。
    • JavaScriptだとコスト度外視できると思うよ。
      • 度外視?
      • JavaScriptの return; って毎回暗黙のundefinedを返してて、それがthisになるぐらい。
  • 兄さん(MARIO)に魔法かけてるよね?
    • うん。そう。そうなんだよね。。。 結局マリオは超えられない仕様みたい。
  • ワリオのRESTが255を超えてるから、一回でも死ぬとゲームオーバー?
    • そこはどうでもいいところだし、副作用として年がバレます。
  • メソッドチェーン? メソッドチェイン?
    • ネイティブの発声に近いのは、メソッドチェイン のほうだと思うので、次回からはそうします。