Javascript是否具有Ruby的method_missing功能?

时间:2012-03-19 23:50:23

标签: javascript ruby metaprogramming

在Ruby中我认为你可以调用一个尚未定义的方法,然后捕获被调用方法的名称,并在运行时处理这个方法。

Javascript可以做同样的事情吗?

8 个答案:

答案 0 :(得分:43)

method_missing 与JavaScript不相符,原因与Python中不存在相同:在两种语言中,方法只是恰好是函数的属性;和对象通常具有不可调用的公共属性。与Ruby对比,其中对象的公共接口是100%方法。

JavaScript中需要一个钩子来捕获对缺失属性的访问,无论它们是否是方法。 Python有它:参见__getattr__特殊方法。

Mozilla提出的__noSuchMethod__提案引入了另一种语言不一致的语言。

JavaScript的前进方向是Proxy mechanism(也在ECMAscript Harmony中),它更接近于customizing attribute access的Python协议,而不是Ruby的 method_missing

答案 1 :(得分:29)

您正在解释的ruby功能称为“method_missing”http://rubylearning.com/satishtalim/ruby_method_missing.htm

这是一个全新的功能,仅在某些浏览器中出现,例如Firefox(在蜘蛛猴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/的这篇文章,了解即将实施的更多细节。

答案 2 :(得分:4)

目前不是,不。有a proposal for ECMAScript Harmony, called proxies,它实现了一个类似的(实际上,功能更强大)功能,但是ECMAScript Harmony尚未推出,可能不会持续几年。

答案 3 :(得分:2)

我已经为javascript创建了一个库,可让您在javascript中使用method_missing 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

答案 4 :(得分:1)

不,javascript中没有元编程功能,直接类似于ruby的method_missing钩子。解释器只是引发一个错误,调用代码可以捕获但是被访问的对象无法检测到。这里有一些关于在运行时定义函数的答案,但这不是一回事。您可以进行大量的元编程,更改对象的特定实例,定义函数,执行memoizing和decorators等功能。但是没有像ruby或python中那样缺少函数的动态元编程。

答案 5 :(得分:1)

我遇到了这个问题,因为如果方法没有出现在第一个对象上,我正在寻找一种方法来接触另一个对象。它不像你的要求那么灵活 - 例如,如果两者都缺少一个方法那么它就会失败。

我正在考虑为一个小型库做这个,我帮助配置extjs对象的方式也使它们更易于测试。我有单独的调用来实际掌握交互对象,并认为这可能是通过有效返回增强类型将这些调用粘在一起的好方法

我可以想到两种方法:

<强>原型

你可以使用原型来做到这一点 - 如果它不在实际对象上,那么它就会落到原型上。如果你想要的函数集通过使用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家伙不仅错误地使用了这个&#39;这个&#39;他们还建立了一个完整的疯狂经典继承类型系统,主要是使用原型和&#39;这个&#39;

这实际上是我第一次使用原型/构造函数而且我有点困惑,你不能只设置原型 - 你还必须使用构造函数。对象(至少在firefox中)有一个魔术场调用__proto,这基本上就是真正的原型。似乎实际的原型场只在施工时使用......多么令人困惑!


复制方法

这种方法可能更昂贵,但对我来说似乎更优雅,也适用于使用this的代码(例如,因此您可以使用它来包装库对象)。它也适用于使用函数/闭包样式编写的东西 - 我只是用这个/构造函数来说明它,以表明它可以用这样的东西。

这里有mods:

    //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,但似乎没有必要。

答案 6 :(得分:0)

您可以使用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的所有属性请求。因此,p.specialPants()将导致错误,因为specialPants返回的是字符串而不是函数。

unknownMethod的真正作用等同于以下内容:

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

之所以可行,是因为函数是javascript中的对象。

奖金

如果知道期望的参数数量,则可以在返回的函数中将它们声明为普通参数。
例如:

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

答案 7 :(得分:-1)

据我所知,您可以先将函数初始化为null然后稍后替换实现来模拟它。

var foo = null;
var bar = function() { alert(foo()); } // Appear to use foo before definition

// ...

foo = function() { return "ABC"; } /* Define the function */
bar(); /* Alert box pops up with "ABC" */

这个技巧类似于实现递归lambda的C#技巧,如here所述。

唯一的缺点是,如果您 在定义之前使用foo,那么您尝试拨打null时会收到错误消息虽然它是一个函数,而不是一个更具描述性的错误消息。但是在你定义函数之前,你会希望得到一些错误消息。