FirefoxのJavaScript実行エンジンであるSpiderMonkeyでは、ECMAScriptの仕様にはないSpiderMonkey固有の拡張機能をいくつか利用できますが、その中の1つとして__noSuchMethod__
があります。
これはRubyなどでいう「メソッドミッシング」を実現するための仕組みで、あるオブジェクトに__noSuchMethod__
という名前のメソッドを定義しておくと、呼び出されたメソッドが存在しなかった時に代わりに__noSuchMethod__
メソッドが呼ばれるというものです。
具体的には以下のように使います。
// インスタンスに直接定義する場合
var object = {};
object.__noSuchMethod__ = function(name, args) {
alert('NO SUCH METHOD: '+name+' , '+JSON.stringify(args));
};
object.toStringg('a', 'b'); // NO SUCH METHOD: toStringg , ["a","b"]
// クラスの一部として定義する場合
function MyClass() {
}
MyClass.prototype.__noSuchMethod__ = function(name, args) {
alert('NO SUCH METHOD: '+name+' , '+JSON.stringify(args));
};
var instance = new MyClass();
instance.toStringg('a', 'b'); // NO SUCH METHOD: toStringg , ["a","b"]
こういった機能はいわゆるメタプログラミングにあたり、普段の開発で頻繁に利用する物ではありませんが、フレームワーク的な物を開発する場面では重宝します。 Ruby on Railsなどの「設定より規約」というルールでよく見られる「このような名前でパラメータを与えておけば自動的に、与えた名前に基づくこのような名前のメソッドが利用可能になる」という仕組みを、より自然な形で実現させられます。
ただ、前述の通りこれはSpiderMonkey固有の機能なので他のJSエンジンでは利用できませんし、SpiderMonkeyにおいてもFirefox 44で廃止されました。
代わりとして、より汎用的なProxy
を使う事が推奨されています。
とはいうものの、Proxy
は__noSuchMethod__
とは全く違う様式を取っており、しかも機能が豊富なので、単純に「__noSuchMethod__
と同じ事だけをしたい」という場合にどうすればよいのか分かりにくいです。
また、__noSuchMethod__
を使っていた箇所の設計を見直してProxy
を適切に使うようにするとしても、それなりに規模が大きいコードの場合、フレームワーク的な基盤部分の設計を大きく変えてしまうと変更の影響範囲が大きくなって後が大変です。
結論を述べると、先のような例であれば、以下のようにしてProxy
で__noSuchMethod__
を代替できます。
// インスタンスに直接定義する場合
var object = {};
object.__noSuchMethod__ = function(name, args) {
alert('NO SUCH METHOD: '+name+' , '+JSON.stringify(args));
};
// 追加箇所:ここから
object = (function(source) {
var cache = {};
return new Proxy(source, {
get: function(target, name) {
if (name in target)
return target[name];
return cache[name] || cache[name] = function(...args) {
return target.__noSuchMethod__.call(this, name, args);
};
}
});
})(object);
// 追加箇所:ここまで
object.toStringg('a', 'b'); // NO SUCH METHOD: toStringg , ["a","b"]
// クラスの一部として定義する場合
function MyClass() {
// 追加箇所:ここから
var cache = {};
return new Proxy(this, {
get: function(target, name) {
if (name in target)
return target[name];
return cache[name] || cache[name] = function(...args) {
return target.__noSuchMethod__.call(this, name, args);
};
}
});
// 追加箇所:ここまで
}
MyClass.prototype.__noSuchMethod__ = function(name, args) {
alert('NO SUCH METHOD: '+name+' , '+JSON.stringify(args));
};
var instance = new MyClass();
instance.toStringg('a', 'b'); // NO SUCH METHOD: toStringg , ["a","b"]
これまで__noSuchMethod__
を使っていたコードに対して、例で「追加箇所」と記した部分を付け加えることで、同等の結果を得られるようになります。
ただし、厳密には全く同一というわけではありません。
__noSuchMethod__
はメソッド呼び出しのみが対象になりますが、Proxy
を使用したバージョンでは「未定義のプロパティにアクセスされたら、__noSuchMethod__
を実行する関数オブジェクトを返す」という形になっているので、undefined
が返される事を期待して単純にvar hasProperty = !!instance.something;
のようにした場合にも影響が出てしまいます。
この例であれば、"something" in instance
のような判別方法を使うという風に、「未定義のプロパティにアクセスするとundefined
だけでなく関数が返ってくる事もある」という前提で対象オブジェクトを処理するように気をつける必要がありますので、ご注意下さい。
また、例をよく読むと分かりますが、最初からProxy
を使って書くのであればさらに無駄のない書き方もできます。
ここではあくまで、これまで__noSuchMethod__
を使っていた既存のそれなりの規模があるコードに対して、最小限の変更で同様の結果を得られるようにするという目的に特化しているために、このようになっています。
このようなアドホックな対応で済ませるよりは、Proxy
のAPI設計に即した使い方になるようにコードの設計を見直す方が基本的には望ましいと言えますので、実行に移すかどうかはメリットとデメリットをよく考えてから判断しましょう。