hagino3000's blog

平成アーカイブス (更新停止)

JavaScriptでRubyのmethod_missingを実装する

追記: Firefoxの実装で既に有る__noSuchMethod__に名前は合せた方が良い、というコメントを頂いたので名前を変えました。

何の役に立つか不明だけど書いてみた*1。Proxyでプロパティアクセスをフックして、存在しない場合は用意しておいた関数プロキシを返す。

上記の処理が書いてあるのはこの部分。

function enableMethodMissing(obj) {

  // 関数プロキシの作成
  var functionHandler = createBaseHandler({});
  functionHandler.get = function(receiver, name) {
    // プロパティアクセスの場合は何も返さない
    return function(){};
  }

  var calledProperty;
  var trapFn = Proxy.createFunction(functionHandler, function() {
    // 実行の場合は obj.methodMissing を呼び出す
    return obj.__noSuchMethod__(calledProperty, Array.prototype.slice.call(arguments));
  });

  // プロパティプロキシの作成
  var propertyAccessHandler = createBaseHandler(obj);
  propertyAccessHandler.get = function(receiver, name) {
    if (obj[name]) {
      return obj[name];
    } else {
      // 存在しないプロパティへのアクセスは関数プロキシを返す
      calledProperty = name;
      return trapFn;
    }
  }
  return Proxy.create(propertyAccessHandler);

  // (略)

method_missing用の関数をわざわざProxy.createFunctionしているのは、プロパティアクセスの時はundefinedを返す、関数呼び出しの時はmethod_missing用の関数実行と処理を分けるため。

使うとこの様になる。

// クラス定義
// コンストラクタの中でthisをごにょごにょする。
var Ninja = function(name) { 
  this.name = name; 
  return enableMethodMissing(this);
}

// 通常のメソッド定義
Ninja.prototype.getName = function() { 
  return this.name; 
}

// 存在しないメソッド呼び出しがされた時の関数
Ninja.prototype.__noSuchMethod__ = function(methodName, args) {
  console.log('method name:' + methodName);
  console.log(args);
};

var sasuke = new Ninja('Sasuke');
sasuke.getName(); // => Sasuke
sasuke.hoge(1,2,3); // => __noSuchMethod__('hoge', [1,2,3])
sasuke.hoge; // => undefined


Proxyが何に使えるか、というのはこの動画がわかりやすかった。

*1:Firefox4以降 or JavaScriptの実験機能を有効にしたChromeでのみ動作します。