後付けでメソッドチェーン - 魔法とスーパールイージ
現在リリース済みの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>
反省会
- メソッドチェーンはいつ頃から?
- コンパイラ言語で毎回thisを返すのって、実はコストとのトレードオフなんだよね。
- JavaScriptだとコスト度外視できると思うよ。
- 度外視?
- JavaScriptの return; って毎回暗黙のundefinedを返してて、それがthisになるぐらい。
- JavaScriptだとコスト度外視できると思うよ。
- 兄さん(MARIO)に魔法かけてるよね?
- うん。そう。そうなんだよね。。。 結局マリオは超えられない仕様みたい。
- ワリオのRESTが255を超えてるから、一回でも死ぬとゲームオーバー?
- そこはどうでもいいところだし、副作用として年がバレます。
- メソッドチェーン? メソッドチェイン?
- ネイティブの発声に近いのは、メソッドチェイン のほうだと思うので、次回からはそうします。