3

I have code that looks like this:

obj.foo(); // obj might, or might not have a `foo` method.

I want to know if I can override what happens when obj.foo is called in my code, for example:

obj.foo = function(){ alert ("Hello"); });
obj.onCallNonExistentMethod = function(){ // I know this is imaginary syntax
    alert("World");
}
obj.foo(); // alerts "Hello"
delete obj.foo;
obj.foo(); // alerts "World" , would TypeError without the method missing handler.

From what I understand, in Ruby that would be method_missing or const_missing or something similar.

Can I override what happens on a call to a nonexistent object method in JavaScript? If I can, how do I do it?

The goal is to validate an API I provide to users so they can use the API safely and I can warn them more clearly on errors.

Benjamin Gruenbaum
  • 270,886
  • 87
  • 504
  • 504
  • Your question title is misleading. I'd have expected somebody to do something like `var undefined = ...;`. – ThiefMaster Jun 21 '12 at 18:39
  • @ThiefMaster, which is perfectly legal, because [`undefined` is not a keyword; it's undefined](http://stackoverflow.com/a/4226440/201952). – josh3736 Jun 21 '12 at 18:40
  • It's not what he's asking for though. Besides that, modern JavaScript engines prevent you from redefining the *global* `undefined`. – ThiefMaster Jun 21 '12 at 18:41
  • [IE9 does.](http://jsfiddle.net/josh3736/xdhzg/) – josh3736 Jun 21 '12 at 18:46

4 Answers4

5

Two years ago Benjamin, you didn't understand the notion of behavioral typing very well did you?

Lucky for you, you'll soon realize that an interface is not structural in JavaScript since writing that question, and even wrote a library that solves the exact question you speak of here, only to never use it.

So, to answer your question:

  • Yes, it's perfectly possible to do this.
  • You probably shouldn't for the case you have given.

How to do it

It is perfectly doable if you only have to support really really (really really) new browsers via the Proxy API.

var realObject = { foo: function(){ alert("Bar"); }); // our fooable api
var api = new Proxy({}, {
    get: function(target, name){
        if(name in target){ // normal API call
            return target[name]; 
        }
        // return whatever you want instead of the method:
        return function(){ alert("Baz"); });
    }
});
api.foo(); //alerts Bar
api.bar(); //alerts Baz
api.IWillNotBuyThisRecordItIsScratched(); // alerts Baz

So, while browser support is very shakey, it

Why you shouldn't

Interfaces convey behavior, above cases like typos that can (and should) be normally caught by static analysis tools like jshint or ternjs.

Tools checking for typos are simply not powerful enough to convey behavior in JavaScript, type checking is considered an anti pattern, generally - in languages like JavaScript, Ruby and Python you know the types you'll get.

Community
  • 1
  • 1
Benjamin Gruenbaum
  • 270,886
  • 87
  • 504
  • 504
2

Unfortunately that's not possible in any widely supported way. The closest you can get is using a proxy function for all calls, i.e. instead of foo.bar() you would use something like foo.invoke('bar'). However, that's pretty ugly and most likely close to what you meant in the first "not interested in" part.

In loosely-typed languages is is pretty common to expect the developer to pass compatible objects though - i.e. getting an error when passing something unexpected is usually fine.


However, if you are lucky enough to use a JS engine which supports ECMAScript-harmony features (obviously IE doesn't), you can use Proxy objects to achieve this. Basically you wrap your object in a Proxy which lets you trap most operations on the object such as iterating it, retrieving a property, or even creating properties. Besides the two links having a look at this answer to this question might be worth a shot.

Community
  • 1
  • 1
ThiefMaster
  • 310,957
  • 84
  • 592
  • 636
1

If you're working with strictly defined objects, you should be using custom objects instead of object literals:

function Foo(bar, baz) {
  this.bar = bar;
  this.baz = baz;
}
Foo.prototype = {
  fizz: function () {
     alert(this.bar + ' ' + this.baz + '!');
  }
};

//elsewhere
f = new Foo('Hello', 'World');
f.fizz();

And then you can tell if the object is a Foo instance by using the instanceof operator:

function Buzz(foo) {
  if (foo instanceof Foo) {
    foo.fizz();
  }
}
f = new Foo('Hello', 'World');
Buzz(f);

If you're simply looking to check whether an object contains a function at a particular parameter, you can use something like:

function hasFunction(obj, param) {
    return (typeof obj[param] === 'function');
}
o = {
    foo: function () {}
}
hasFunction(o, 'foo'); //true
hasFunction(o, 'bar'); //false
zzzzBov
  • 174,988
  • 54
  • 320
  • 367
  • Thanks for your response but I specifically said I wanted to avoid the constructor pattern. What I'm trying to do is save the extra boilerplate of 'hasFunction' – Benjamin Gruenbaum Jun 21 '12 at 18:43
  • if you can guarantee that the parameter will be a function or non-existent, you could use `foo.bar&&foo.bar()` as a shorthand, which is functionally identical to `if (foo.bar) { foo.bar() }`. – zzzzBov Jun 21 '12 at 18:45
1

This is not really useful today (due to spotty browser support), but the Proxy API will allow you to write code that intercepts property access on objects. In other words, when you write:

obj.foo();

The proxy's trap can then return whatever it wants (to be invoked), regardless of whether the property actually exists on the original object.

josh3736
  • 139,160
  • 33
  • 216
  • 263