55

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.

Can Javascript do the same kind of thing ?

Peter Lyons
  • 142,938
  • 30
  • 279
  • 274
user310291
  • 36,946
  • 82
  • 271
  • 487
  • I'm not sure if you are referring to function prototypes, in which case maybe this is of use? https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function To be honest I have no idea if that reference is useful I've only ever seen function prototypes in C++ – Joel Mar 19 '12 at 23:53
  • possible duplicate of [Capture method missing in Javascript and do some logic?](http://stackoverflow.com/questions/8283362/capture-method-missing-in-javascript-and-do-some-logic) – Paul Sweatte Sep 11 '14 at 01:19

8 Answers8

45

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; and objects often have public attributes that are not callable. Contrast with Ruby, where the public interface of an object is 100% methods.

What is needed in JavaScript is a hook to catch access to missing attributes, whether they are methods or not. Python has it: see the __getattr__ special method.

The __noSuchMethod__ proposal by Mozilla introduced yet another inconsistency in a language riddled with them.

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.

Luciano Ramalho
  • 1,981
  • 18
  • 22
  • 3
    Note that Javascript semantics is a bit different and more tricky than in Python. In Python `f=obj.m;f(x)` is equivalent to `obj.m(x)`. In Javascript, `obj.m(x)` sets `this` to `obj`, while `f=obj.m;f(x)` doesn't. – ehabkost Jan 29 '13 at 15:15
  • Amen: "The __noSuchMethod__ proposal by Mozilla introduced yet another inconsistency in a language riddled with them." – user2398029 Oct 06 '13 at 17:47
  • Well I don't know about back then, but python now has a really handy `__missing__` method. – aliqandil May 29 '19 at 15:38
  • The `__missing__` method is only used in mappings, to add logic to handle the case when a key is missing in the mapping. For example, it is useful to implement a mapping that works with string keys but is case-insensitve. It has nothing to do with `method_missing`. – Luciano Ramalho Mar 30 '20 at 21:22
31

The ruby feature that you are explaining is called "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). In SpiderMonkey it's called "__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.

jsbueno
  • 99,910
  • 10
  • 151
  • 209
lmmendes
  • 1,480
  • 15
  • 14
  • Thanks for the articles I thought I would be baffled by my weird question - as I am often here when I asked metaprogramming questions :) - I'm happy that js gurus think about it. – user310291 Mar 20 '12 at 07:09
  • 19
    The Yehuda Katz article is from 2008. Brandon Eich has advocated the Proxy API [since 2010](http://jsconf.eu/2010/speaker/be_proxy_objects.html). The [\_\_noSuchMethod__](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/noSuchMethod) API proposed by Mozilla is non-standard and has no future. – Luciano Ramalho Jan 29 '13 at 15:31
6

You can use the Proxy class.

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.

You should be careful with get because it intercepts all attribute requests of p. So, p.specialPants() would result in an error because specialPants returns a string and not a function.

What's really going on with unknownMethod is equivalent to the following:

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

This works because functions are objects in 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) {
...
Lindsay-Needs-Sleep
  • 1,304
  • 1
  • 15
  • 26
4

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.

Jörg W Mittag
  • 363,080
  • 75
  • 446
  • 653
  • 2
    Proxies are currently implemented in Chrome 21 and onwards with the experimental Javascript flag. See this site for up to date information on what ECMAScript Harmony features are currently supported: http://kangax.github.io/es5-compat-table/es6/ – stephenbez Jun 06 '13 at 23:06
  • 1
    @jörg-w-mittag, the future is almost here :) – JackDev Dec 04 '14 at 12:05
  • 1
    @HappyHamburger: I am pretty excited about ES6, specifically Proper Tail Calls, `let` and `const`, concise function syntax, and Proxies. – Jörg W Mittag Dec 04 '14 at 12:08
  • @jörg-w-mittag - What do you think about the implementation of classes in the new spec? – JackDev Dec 04 '14 at 14:43
  • @HappyHamburger: I use ES more as a blend of Scheme and Self rather than an in-browser Java-clone, so I don't much care for classes. However, since they are just syntactic sugar, they don't change the core of ES at all. This is very much unlike ES4 classes, which were basically an entirely new inheritance construct in addition to prototypes. – Jörg W Mittag Dec 04 '14 at 15:31
3

I've created a library for javascript that let you use method_missing in javascript: https://github.com/ramadis/unmiss

It uses ES6 Proxies to work. Here is an example using ES6 Class inheritance. 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
Rama
  • 72
  • 3
1

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. 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.

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'.

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. 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). 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.

Here's the 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());

I was actually anticipating having to use bind in there somewhere but it appears not to be necessary.

JonnyRaa
  • 7,559
  • 6
  • 45
  • 49
1

No, there is no metaprogramming capability in javascript directly analogous to ruby's method_missing hook. 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.

Peter Lyons
  • 142,938
  • 30
  • 279
  • 274
-1

Not to my knowledge, but you can simulate it by initializing the function to null at first and then replacing the implementation later.

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.

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. But you would expect to get some error message for using a function before it's defined.

Adam Mihalcin
  • 14,242
  • 4
  • 36
  • 52
  • 1
    Still not I want since it must be done purely at runtime whereas in your example you must define foo at design time whereas I may not even know the name foo at that time. – user310291 Mar 21 '12 at 07:24
  • "you would expect to get some error message for using a function before it's defined" - no, you wouldn't. That's the whole point of method-missing. – James Moore Jul 31 '12 at 18:25
  • You don't need to initialize foo to null at the top. The declaration will get hoisted anyway. As long as it is set before bar is called. Anyway, this doesn't really answer the OP's question.. – sstur Apr 12 '13 at 09:41