web-dev-qa-db-ja.com

JavaScriptにはRubyのmethod_missing機能のようなものがありますか?

Rubyでは、定義されていないメソッドを呼び出しても、呼び出されたメソッドの名前をキャプチャし、実行時にこのメソッドの処理を実行できると思います。

JavaScriptでも同じようなことができますか?

48
user310291

説明しているRuby機能は「method_missing」と呼ばれます http://rubylearning.com/satishtalim/Ruby_method_missing.htm

これは、Firefoxなどの一部のブラウザー(spider monkey JavaScriptエンジン)にのみ存在する新しい機能です。 SpiderMonkeyでは「__noSuchMethod__」と呼ばれます https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/NoSuchMethod

今後の実装の詳細については、Yehuda Katz http://yehudakatz.com/2008/08/18/method_missing-in-javascript/ からこの記事をお読みください。

29
lmmendes

method_missingは、Pythonには存在しないのと同じ理由でJavaScriptにうまく適合しません。両方の言語で、メソッドは単なる関数であり、単なる属性です。多くの場合、オブジェクトには呼び出しできないパブリック属性があります。オブジェクトのパブリックインターフェイスが100%メソッドであるRubyとは対照的です。

JavaScriptで必要なのは、メソッドかどうかに関係なく、欠落している属性へのアクセスをキャッチするフックです。 Pythonある: __ getattr __ 特殊メソッドを参照してください。

Mozillaによる __ noSuchMethod __ 提案は、それらに取り付かれた言語にさらに別の矛盾をもたらしました。

JavaScriptの将来の道は プロキシメカニズムECMAscript Harmony でもある)で、Pythonのプロトコル 属性アクセスのカスタマイズ Rubyのmethod_missingよりも。

43
Luciano Ramalho

現時点ではありません。 ECMAScript Harmonyのproxies と呼ばれる提案があり、これは同様の(実際には、はるかに多くのパワフル)機能ですが、ECMAScript Harmonyはまだリリースされておらず、おそらく数年はリリースされません。

4
Jörg W Mittag

JavaScriptでmethod_missingを使用できるようにするJavaScriptのライブラリを作成しました:https://github.com/ramadis/unmiss

ES6プロキシを使用して動作します。 ES6クラスの継承を使用した例を次に示します。ただし、デコレータを使用して同じ結果を得ることができます。

import { MethodMissingClass } from 'unmiss'

class Example extends MethodMissingClass {
    methodMissing(name, ...args) {
        console.log(`Method ${name} was called with arguments: ${args.join(' ')}`);
    }
}

const instance = new Example;
instance.what('is', 'this');

> Method what was called with arguments: is this
2
Rama

いいえ、Rubyのmethod_missingフックに直接類似したメタプログラミング機能はJavaScriptにはありません。インタープリターは、呼び出しコードがキャッチできるが、アクセスされているオブジェクトによって検出できないエラーを発生させるだけです。実行時に関数を定義することについていくつかの答えがありますが、それは同じことではありません。多くのメタプログラミング、オブジェクトの特定のインスタンスの変更、関数の定義、メモ化やデコレーターなどの機能的なことを行うことができます。しかし、Rubyまたはpythonのように、不足している関数の動的なメタプログラミングはありません。

1
Peter Lyons

最初のオブジェクトにメソッドが存在しない場合に別のオブジェクトにフォールスルーする方法を探していたため、この質問に行きました。それはあなたが求めるものほど柔軟ではありません-たとえば、メソッドが両方から欠落している場合、それは失敗します。

私が持っている小さなライブラリでこれを行うことを考えていました。これは、extjsオブジェクトをよりテストしやすい方法で構成するのに役立ちます。私は実際に対話のためにオブジェクトを取得するために別々の呼び出しを持っていて、これは拡張型を効果的に返すことによってこれらの呼び出しをまとめる良い方法かもしれないと思いました

これを行うには2つの方法が考えられます。

プロトタイプ

これはプロトタイプを使用して行うことができます。実際のオブジェクト上にない場合は、プロトタイプまで処理が進むためです。 thisキーワードを使用するためにドロップスルーする一連の関数の場合、これは機能しないようです。明らかに、オブジェクトは他のオブジェクトが知っていることを知らないか、気にしません。

そのすべてが独自のコードであり、これとコンストラクタを使用していない場合...これは多くの理由から良いアイデアであり、次のように実行できます。

    var makeHorse = function () {
        var neigh = "neigh";

        return {
            doTheNoise: function () {
                return neigh + " is all im saying"
            },
            setNeigh: function (newNoise) {
                neigh = newNoise;
            }
        }
    };

    var createSomething = function (fallThrough) {
        var constructor = function () {};
        constructor.prototype = fallThrough;
        var instance = new constructor();

        instance.someMethod = function () {
            console.log("aaaaa");
        };
        instance.callTheOther = function () {
            var theNoise = instance.doTheNoise();
            console.log(theNoise);
        };

        return instance;
    };

    var firstHorse = makeHorse();
    var secondHorse = makeHorse();
    secondHorse.setNeigh("mooo");

    var firstWrapper = createSomething(firstHorse);
    var secondWrapper = createSomething(secondHorse);
    var nothingWrapper = createSomething();

    firstWrapper.someMethod();
    firstWrapper.callTheOther();
    console.log(firstWrapper.doTheNoise());

    secondWrapper.someMethod();
    secondWrapper.callTheOther();
    console.log(secondWrapper.doTheNoise());

    nothingWrapper.someMethod();
    //this call fails as we dont have this method on the fall through object (which is undefined)
    console.log(nothingWrapper.doTheNoise());

Extjsの人たちは誤って「this」を使用しただけでなく、プロトタイプと「this」を使用するという原則に基づいて、まったく変わった古典的な継承型システムを構築したため、これは私のユースケースでは機能しません。

これは実際にプロトタイプ/コンストラクターを使用したのが初めてであり、プロトタイプを設定できないだけで少し困惑しました。コンストラクターも使用する必要があります。オブジェクト(少なくともFirefoxでは)には基本的に実際のプロトタイプである__protoを呼び出す魔法のフィールドがあります。実際のプロトタイプフィールドは、構築時にしか使用されないようです。


コピー方法

このメソッドはおそらくより高価ですが、私にはよりエレガントに見え、thisを使用しているコードでも機能します(たとえば、ライブラリオブジェクトをラップするために使用できます)。また、機能/閉鎖スタイルを使用して記述されたものにも機能します。このようなもので機能することを示すために、これをコンストラクター/コンストラクターで示しました。

ここに改造があります:

    //this is now a constructor
    var MakeHorse = function () {
        this.neigh = "neigh";
    };

    MakeHorse.prototype.doTheNoise = function () {
        return this.neigh + " is all im saying"
    };
    MakeHorse.prototype.setNeigh = function (newNoise) {
        this.neigh = newNoise;
    };

    var createSomething = function (fallThrough) {
        var instance = {
            someMethod : function () {
                console.log("aaaaa");
            },
            callTheOther : function () {
                //note this has had to change to directly call the fallThrough object
                var theNoise = fallThrough.doTheNoise();
                console.log(theNoise);
            }
        };

        //copy stuff over but not if it already exists
        for (var propertyName in fallThrough)
            if (!instance.hasOwnProperty(propertyName))
                instance[propertyName] = fallThrough[propertyName];

        return instance;
    };

    var firstHorse = new MakeHorse();
    var secondHorse = new MakeHorse();
    secondHorse.setNeigh("mooo");

    var firstWrapper = createSomething(firstHorse);
    var secondWrapper = createSomething(secondHorse);
    var nothingWrapper = createSomething();

    firstWrapper.someMethod();
    firstWrapper.callTheOther();
    console.log(firstWrapper.doTheNoise());

    secondWrapper.someMethod();
    secondWrapper.callTheOther();
    console.log(secondWrapper.doTheNoise());

    nothingWrapper.someMethod();
    //this call fails as we dont have this method on the fall through object (which is undefined)
    console.log(nothingWrapper.doTheNoise());

私は実際にはどこかでbindを使用する必要があると予想していましたが、必要ないようです。

1
JonnyRaa

Proxy クラスを使用できます。

_var myObj = {
    someAttr: 'foo'
};

var p = new Proxy(myObj, {
    get: function (target, methodOrAttributeName) {
        // target is the first argument passed into new Proxy, aka. target is myObj

        // First give the target a chance to handle it
        if (Object.keys(target).indexOf(methodOrAttributeName) !== -1) {
            return target[methodOrAttributeName];
        }

        // If the target did not have the method/attribute return whatever we want

        // Explicitly handle certain cases
        if (methodOrAttributeName === 'specialPants') {
            return 'trousers';
        }

        // return our generic method_missing function
        return function () {
            // Use the special "arguments" object to access a variable number arguments
            return 'For show, myObj.someAttr="' + target.someAttr + '" and "'
                   + methodOrAttributeName + '" called with: [' 
                   + Array.prototype.slice.call(arguments).join(',') + ']';
        }
    }
});

console.log(p.specialPants);
// outputs: trousers

console.log(p.unknownMethod('hi', 'bye', 'ok'));
// outputs: 
// For show, myObj.someAttr="foo" and "unknownMethod" called with: [hi,bye,ok]
_

pの代わりにmyObjを使用します。

getは、pのすべての属性要求をインターセプトするため、注意が必要です。したがって、specialPantsは関数ではなく文字列を返すため、p.specialPants()はエラーになります。

unknownMethodで実際に起こっていることは、以下と同等です。

_var unk = p.unkownMethod;
unk('hi', 'bye', 'ok');
_

これは、関数がJavaScriptのオブジェクトであるため機能します。

ボーナス

期待する引数の数がわかっている場合は、返される関数で通常どおりに宣言できます。
例えば:

_...
get: function (target, name) {
    return function(expectedArg1, expectedArg2) {
...
_
0