91

Long story short: I'm in a situation where I'd like a PHP-style getter, but in JavaScript.

My JavaScript is running in Firefox only, so Mozilla specific JS is OK by me.

The only way I can find to make a JS getter requires specifying its name, but I'd like to define a getter for all possible names. I'm not sure if this is possible, but I'd very much like to know.

arantius
  • 1,715
  • 1
  • 17
  • 28

9 Answers9

123

Proxy can do it! I'm so happy this exists!! An answer is given here: Is there a javascript equivalent of python's __getattr__ method? . To rephrase in my own words:

var x = new Proxy({}, {
  get(target, name) {
    return "Its hilarious you think I have " + name
  }
})

console.log(x.hair) // logs: "Its hilarious you think I have hair"

Proxy for the win! Check out the MDN docs: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy

Works in chrome, firefox, and node.js. Downsides: doesn't work in IE - freakin IE. Soon.

user2226755
  • 12,494
  • 5
  • 50
  • 73
B T
  • 57,525
  • 34
  • 189
  • 207
  • It works in Edge too. See [caniuse.com](https://caniuse.com/#feat=proxy) for specific browser versions. – styfle Dec 28 '17 at 20:21
  • but what if I want to mimic a function call, like myProxy.foo('bar'). I've seen there is apply for functions but is not allowed to use custom names, is always executed on the proxy: myProxy('bar'), we could mimic it with myProxy('foo', 'bar') but then we are missing the magic thing – Enrique Dec 15 '18 at 13:54
  • 3
    @Enrique If you return a function from the getter, you should absolutely be able to call `myProxy.foo('bar')`. – B T Dec 17 '18 at 22:13
  • I recognize the answer is correct for the question, but for readers like me who want a default return value, beware that the proxy takes precedence. – Barry McNamara Aug 21 '20 at 18:26
  • @BarryMcNamara Could you clarify what you mean that the proxy takes precedence? – B T Aug 25 '20 at 20:39
  • The handler traps calls to the target object. If your target has properties which you want to continue to be gettable, you need to explicitly return them from the handler's get method. – Barry McNamara Aug 26 '20 at 22:24
74

You can combine proxy and class to have a nice looking code like php:

class Magic {
    constructor () {
        return new Proxy(this, this);
    }
    get (target, prop) {
        return this[prop] || 'MAGIC';
    }
}

this binds to the handler, so you can use this instead of target.

Note: unlike PHP, proxy handles all prop access.

let magic = new Magic();
magic.foo = 'NOT MAGIC';
console.log(magic.foo); // NOT MAGIC
console.log(magic.bar); // MAGIC

You can check which browsers support proxy http://caniuse.com/#feat=proxy.

Ali
  • 21,572
  • 15
  • 83
  • 95
46

The closest you can find is __noSuchMethod__ (__noSuchMethod__ is deprecated), which is JavaScript's equivalent of PHP's __call().

Unfortunately, there's no equivalent of __get/__set, which is a shame, because with them we could have implemented __noSuchMethod__, but I don't yet see a way to implement properties (as in C#) using __noSuchMethod__.

var foo = {
    __noSuchMethod__ : function(id, args) {
        alert(id);
        alert(args);
    }
};

foo.bar(1, 2);
Ionuț G. Stan
  • 176,118
  • 18
  • 189
  • 202
  • 1
    I was actually trying to create a "message eating nil" equivalent, and this does it *exactly*! Thanks! – arantius Jun 16 '09 at 01:36
  • I had no idea about the "message eating nil" concept. Thanks – Ionuț G. Stan Jun 16 '09 at 05:33
  • What about nowjs? They have to be using some sort of __set() and __get() equivalent to make their RPC-style framework actually work. I can't tell how it works from the source code. – BMiner Aug 08 '11 at 16:34
  • is there something similar for Chrome/Awesomium? I also found another concept using [Harmony Proxies](http://stackoverflow.com/a/3757676/648980), which unfortunately only works in FF right now. – Kuepper Mar 12 '12 at 10:42
  • 4
    @Towa `Proxy` is the future indeed. It's available on Chrome if you enable experimental JavaScript features under "about:flags". There's no other equivalent for `__noSuchMethod__`. – Ionuț G. Stan Mar 12 '12 at 11:10
  • 10
    __noSuchMethod__ is non standard and no version of Internet Explorer support it, so it may not work on many of your users' browser. – Soroush Nov 22 '13 at 18:12
  • 2
    What about ECMAScript analog of this? – vp_arth Dec 27 '13 at 18:32
  • 8
    **IMPORTANT:** The link says now: *This feature is obsolete. Although it may still work in some browsers, its use is discouraged since it could be removed at any time. Try to avoid using it.* – Wilt Jan 25 '16 at 12:32
  • 7
    While __noSuchMethod__ has been dropped, the ECMAScript 2015 (ES6) specification has the Proxy object, with which you can achieve the below (and more). – Albert James Teddy Aug 11 '16 at 12:56
  • What is the current status of __noSuchMethod__ ? I cannot find in caniuse, and I think is much better than Proxy, because with proxy we cannot fake a function call – Enrique Dec 15 '18 at 13:55
5

If you really need an implementation that works, you could "cheat" your way arround by testing the second parameter against undefined, this also means you could use get to actually set parameter.

var foo = {
    args: {},

    __noSuchMethod__ : function(id, args) {
        if(args === undefined) {
            return this.args[id] === undefined ? this[id] : this.args[id]
        }

        if(this[id] === undefined) {
            this.args[id] = args;
        } else {
            this[id] = args;
        }
    }
};
Stephan
  • 41,764
  • 65
  • 238
  • 329
Vengarioth
  • 684
  • 5
  • 13
5

Javascript 1.5 does have getter/setter syntactic sugar. It's explained very well by John Resig here

It's not generic enough for web use, but certainly Firefox has them (also Rhino, if you ever want to use it on the server side).

Javier
  • 60,510
  • 8
  • 78
  • 126
  • 7
    Not quite __get() and __set(). PHP's version let you monitor ALL properties, even ones that haven't been created yet. – BMiner Aug 08 '11 at 16:25
1

If you're looking for something like PHP's __get() function, I don't think Javascript provides any such construct.

The best I can think of doing is looping through the object's non-function members and then creating a corresponding "getXYZ()" function for each.

In dodgy pseudo-ish code:

for (o in this) {
    if (this.hasOwnProperty(o)) {
        this['get_' + o] = function() {
            // return this.o -- but you'll need to create a closure to
            // keep the correct reference to "o"
        };
    }
}
nickf
  • 537,072
  • 198
  • 649
  • 721
0

I ended up using a nickfs' answer to construct my own solution. My solution will automatically create get_{propname} and set_{propname} functions for all properties. It does check if the function already exists before adding them. This allows you to override the default get or set method with our own implementation without the risk of it getting overwritten.

for (o in this) {
        if (this.hasOwnProperty(o)) {
            var creategetter = (typeof this['get_' + o] !== 'function');
            var createsetter = (typeof this['set_' + o] !== 'function');
            (function () {
                var propname = o;
                if (creategetter) {
                    self['get_' + propname] = function () {
                        return self[propname];
                    };
                }
                if (createsetter) {
                    self['set_' + propname] = function (val) {
                        self[propname] = val;
                    };
                }
            })();
        }
    }
Kevin
  • 9
  • 2
0

This is not exactly an answer to the original question, however this and this questions are closed and redirect here, so here I am. I hope I can help some other JS newbie that lands here as I did.

Coming from Python, what I was looking for was an equivalent of obj.__getattr__(key)and obj.__hasattr__(key) methods. What I ended up using is: obj[key] for getattr and obj.hasOwnProperty(key) for hasattr (doc).

Théo Rubenach
  • 484
  • 1
  • 4
  • 12
0

It is possible to get a similar result simply by wrapping the object in a getter function:

const getProp = (key) => {
  const dictionary = {
    firstName: 'John',
    lastName: 'Doe',
    age: 42,
    DEFAULT: 'there is no prop like this'
  }
  return (typeof dictionary[key] === 'undefined' ? dictionary.DEFAULT : dictionary[key]);
}

console.log(getProp('age')) // 42

console.log(getProp('Hello World')) // 'there is no prop like this'
HynekS
  • 2,738
  • 1
  • 19
  • 34