简体   繁体   English

Javascript是否具有类似Ruby的method_missing功能?

[英]Does Javascript have something like Ruby's method_missing feature?

In Ruby I think you can call a method that hasn't been defined and yet capture the name of the method called and do processing of this method at runtime. 我认为在Ruby中,您可以调用一个尚未定义的方法,然后捕获被调用方法的名称,并在运行时对该方法进行处理。

Can Javascript do the same kind of thing ? Javascript可以做同样的事情吗?

method_missing does not fit well with JavaScript for the same reason it does not exist in Python: in both languages, methods are just attributes that happen to be functions; 由于Python中不存在method_missing ,因此method_missing不太适合JavaScript:在两种语言中,方法只是碰巧是函数的属性; and objects often have public attributes that are not callable. 对象通常具有不可调用的公共属性。 Contrast with Ruby, where the public interface of an object is 100% methods. 与Ruby相比,Ruby的对象的公共接口是100%方法。

What is needed in JavaScript is a hook to catch access to missing attributes, whether they are methods or not. JavaScript中需要的是一个钩子,用于捕获对缺失属性的访问,无论它们是否为方法。 Python has it: see the __getattr__ special method. Python拥有它:请参见__getattr__特殊方法。

The __noSuchMethod__ proposal by Mozilla introduced yet another inconsistency in a language riddled with them. Mozilla的__noSuchMethod__提议在与他们混在一起的语言中引入了另一个不一致之处。

The way forward for JavaScript is the Proxy mechanism (also in ECMAscript Harmony ), which is closer to the Python protocol for customizing attribute access than to Ruby's method_missing . JavaScript的前进之路是代理机制 (也在ECMAscript Harmony中 ),它比Python协议自定义属性访问更接近Ruby的method_missing

The ruby feature that you are explaining is called "method_missing" http://rubylearning.com/satishtalim/ruby_method_missing.htm . 您要解释的红宝石功能称为“ method_missing” http://rubylearning.com/satishtalim/ruby_method_missing.htm

It's a brand new feature that is present only in some browsers like Firefox (in the spider monkey Javascript engine). 这是一项全新功能,仅在某些浏览器(如Firefox)(蜘蛛猴Javascript引擎)中提供。 In SpiderMonkey it's called "__noSuchMethod__" https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/NoSuchMethod 在SpiderMonkey中,它称为“ __noSuchMethod__” https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/NoSuchMethod

Please read this article from Yehuda Katz http://yehudakatz.com/2008/08/18/method_missing-in-javascript/ for more details about the upcoming implementation. 请阅读Yehuda Katz的这篇文章,网址为http://yehudakatz.com/2008/08/18/method_missing-in-javascript/ ,以了解有关即将实施的更多信息。

Not at the moment, no. 目前不行。 There is a proposal for ECMAScript Harmony, called proxies , which implements a similar (actually, much more powerful) feature, but ECMAScript Harmony isn't out yet and probably won't be for a couple of years. ECMAScript Harmony一个建议书,称为proxies ,它实现了类似的功能(实际上,功能更强大),但是ECMAScript Harmony尚未发布,并且可能会持续几年。

I've created a library for javascript that let you use method_missing in javascript: https://github.com/ramadis/unmiss 我已经为javascript创建了一个库,可让您在javascript中使用method_missinghttps : //github.com/ramadis/unmiss

It uses ES6 Proxies to work. 它使用ES6代理进行工作。 Here is an example using ES6 Class inheritance. 这是一个使用ES6类继承的示例。 However you can also use decorators to achieve the same results. 但是,您也可以使用装饰器来达到相同的结果。

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

I came to this question because I was looking for a way to fall through to another object if the method wasn't present on the first object. 我之所以提出这个问题,是因为我正在寻找一种方法,以解决第一个对象上没有该方法的问题。 It's not quite as flexible as what your asking - for instance if a method is missing from both then it will fail. 它不像您的要求那样灵活-例如,如果两种方法都缺失,那么它将失败。

I was thinking of doing this for a little library I've got that helps configure extjs objects in a way that also makes them more testable. 我正在考虑为我拥有的一个小库执行此操作,该库有助于以某种方式配置extjs对象,并使它们更具可测试性。 I had seperate calls to actually get hold of the objects for interaction and thought this might be a nice way of sticking those calls together by effectively returning an augmented type 我有单独的调用来实际获取用于交互的对象,并认为这可能是通过有效地返回增强类型将这些调用组合在一起的一种好方法

I can think of two ways of doing this: 我可以想到两种方法:

Prototypes 样机

You can do this using prototypes - as stuff falls through to the prototype if it isn't on the actual object. 您可以使用原型进行此操作-因为如果原型不在实际对象上,则东西会落入原型。 It seems like this wouldn't work if the set of functions you want drop through to use the this keyword - obviously your object wont know or care about stuff that the other one knows about. 如果要删除的一组函数使用this关键字,这似乎将不起作用-显然,您的对象将不知道或不在乎另一个人知道的东西。

If its all your own code and you aren't using this and constructors ... which is a good idea for lots of reasons then you can do it like 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());

This doesn't work for my use case as the extjs guys have not only mistakenly used 'this' they've also built a whole crazy classical inheritance type system on the principal of using prototypes and 'this'. 这对我的用例不起作用,因为extjs家伙不仅错误地使用了“ this”,他们还基于使用原型和“ this”的原理构建了一个疯狂的经典继承类型系统。

This is actually the first time I've used prototypes/constructors and I was slightly baffled that you can't just set the prototype - you also have to use a constructor. 这实际上是我第一次使用原型/构造函数,而我对不能只设置原型感到困惑,还必须使用构造函数。 There is a magic field in objects (at least in firefox) call __proto which is basically the real prototype. 在对象中(至少在firefox中)有一个魔域,称为__proto,这基本上是真实的原型。 it seems the actual prototype field is only used at construction time... how confusing! 似乎实际的原型字段仅在构建时使用...多么令人困惑!


Copying methods 复制方式

This method is probably more expensive but seems more elegant to me and will also work on code that is using this (eg so you can use it to wrap library objects). 该方法可能更昂贵,但对我来说似乎更优雅,并且也可以在使用this代码上工作(例如,因此您可以使用它包装库对象)。 It will also work on stuff written using the functional/closure style aswell - I've just illustrated it with this/constructors to show it works with stuff like that. 它也可以在使用功能/闭包样式编写的东西上工作-我刚刚用this /构造函数对其进行了说明,以表明它可以与类似的东西一起工作。

Here's the mods: 这是mod:

    //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());

I was actually anticipating having to use bind in there somewhere but it appears not to be necessary. 我实际上预期必须在某处使用bind ,但这似乎不是必需的。

No, there is no metaprogramming capability in javascript directly analogous to ruby's method_missing hook. 不,在JavaScript中没有直接类似于ruby的method_missing钩子的元编程功能。 The interpreter simply raises an Error which the calling code can catch but cannot be detected by the object being accessed. 解释器仅引发一个错误,调用代码可以捕获该错误,但是无法被正在访问的对象检测到。 There are some answers here about defining functions at run time, but that's not the same thing. 关于在运行时定义函数,这里有一些答案,但这不是一回事。 You can do lots of metaprogramming, changing specific instances of objects, defining functions, doing functional things like memoizing and decorators. 您可以执行许多元编程,更改对象的特定实例,定义函数,执行诸如记忆和修饰符之类的功能。 But there's no dynamic metaprogramming of missing functions as there is in ruby or python. 但是没有像ruby或python中那样缺少函数的动态元编程。

You can use the Proxy class. 您可以使用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]

About 关于

You would use p in place of myObj . 您可以使用p代替myObj

You should be careful with get because it intercepts all attribute requests of p . 您应该对get小心,因为它会拦截p所有属性请求。 So, p.specialPants() would result in an error because specialPants returns a string and not a function. 因此, p.specialPants()将导致错误,因为specialPants返回的是字符串而不是函数。

What's really going on with unknownMethod is equivalent to the following: unknownMethod的真正unknownMethod等同于以下内容:

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

This works because functions are objects in javascript. 之所以可行,是因为函数是javascript中的对象。

Bonus 奖金

If you know the number of arguments you expect, you can declare them as normal in the returned function. 如果知道期望的参数数量,则可以在返回的函数中将它们声明为正常参数。
eg: 例如:

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

Not to my knowledge, but you can simulate it by initializing the function to null at first and then replacing the implementation later. 就我所知,您可以通过首先将函数初始化为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" */

This trick is similar to a C# trick for implementing recursive lambdas, as described here . 这招是类似于C#技巧实现递归lambda表达式,如所描述这里

The only downside is that if you do use foo before it's defined, you'll get an error for trying to call null as though it were a function, rather than a more descriptive error message. 唯一的缺点是,如果您在定义foo之前确实使用了foo ,则会因为尝试调用null而不是更具描述性的错误消息而收到错误,因为它试图将它当作一个函数来调用。 But you would expect to get some error message for using a function before it's defined. 但是您希望在定义函数之前会收到一些使用函数的错误消息。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM