Is it possible to implement dynamic getters / setters in JavaScript?

I know how to create getters and setters for properties whose names are already known, doing something like this:

// A trivial example: function MyObject(val){ this.count = 0; this.value = val; } MyObject.prototype = { get value(){ return this.count < 2 ? "Go away" : this._value; }, set value(val){ this._value = val + (++this.count); } }; var a = new MyObject('foo'); alert(a.value); // --> "Go away" a.value = 'bar'; alert(a.value); // --> "bar2" 

Now, to my question, is it possible to somehow define such tricks and setter tricks? Ie, create getters and setters for any property name that is not yet defined.

The concept is possible in PHP using the magic methods __get() and __set() (see the PHP documentation for information on this), so I'm really asking if there is a JavaScript equivalent there?

Needless to say, I would ideally like a cross-browser compatible solution.

+72
javascript metaprogramming getter-setter
25 Oct '11 at 15:45
source share
4 answers

Update for 2013 and 2015 (see initial response from 2011 below):

This has changed as an ES2015 specification (aka ES6): JavaScript now has a proxy . Proxies allow you to create objects that are true proxy servers for (facades) of other objects. Here is a simple example that turns any property values ​​that are strings for all caps upon retrieval:

 var original = { "foo": "bar" }; var proxy = new Proxy(original, { get: function(target, name, receiver) { var rv = target[name]; if (typeof rv === "string") { rv = rv.toUpperCase(); } return rv; } }); console.log("original.foo = " + original.foo); // "bar" console.log("proxy.foo = " + proxy.foo); // "BAR" 

 "use strict"; (function() { if (typeof Proxy == "undefined") { console.log("This browser doesn't support Proxy"); return; } var original = { "foo": "bar" }; var proxy = new Proxy(original, { get: function(target, name, receiver) { var rv = target[name]; if (typeof rv === "string") { rv = rv.toUpperCase(); } return rv; } }); console.log("original.foo = " + original.foo); // "bar" console.log("proxy.foo = " + proxy.foo); // "BAR" })(); 

Operations that you do not override have default behavior. In the above example, we all redefine get , but there is a whole list of operations that you can connect to.

In the argument list of the get handler function:

  • target is an object that is proxied ( original , in our case).
  • name is (of course) the name of the returned property.
  • receiver - either the proxy itself, or something that inherits from it. In our case, receiver is === proxy , but if proxy used as a prototype, receiver could be a descendant, therefore, it was on the function signature (but at the end, so you can easily refuse it if, as in our example above, you are not actually using it).

This allows you to create an object with the required getter and setter function:

 var obj = new Proxy({}, { get: function(target, name) { if (!(name in target)) { console.log("Getting non-existant property '" + name + "'"); return undefined; } return target[name]; }, set: function(target, name, value) { if (!(name in target)) { console.log("Setting non-existant property '" + name + "', initial value: " + value); } target[name] = value; return true; } }); console.log("[before] obj.foo = " + obj.foo); obj.foo = "bar"; console.log("[after] obj.foo = " + obj.foo); 

 "use strict"; (function() { if (typeof Proxy == "undefined") { console.log("This browser doesn't support Proxy"); return; } var obj = new Proxy({}, { get: function(target, name) { if (!(name in target)) { console.log("Getting non-existant property '" + name + "'"); return undefined; } return target[name]; }, set: function(target, name, value) { if (!(name in target)) { console.log("Setting non-existant property '" + name + "', initial value: " + value); } target[name] = value; return true; } }); console.log("[before] obj.foo = " + obj.foo); obj.foo = "bar"; console.log("[after] obj.foo = " + obj.foo); })(); 

(Notice how I left the receiver functions, since we are not using it. receiver is an optional fourth argument to arg on set .)

The conclusion above:

  Getting non-existant property 'foo'
 [before] obj.foo = undefined
 Setting non-existant property 'foo', initial value: bar
 [after] obj.foo = bar 

Pay attention to how we get the message "nonexistent" when we try to extract foo when it does not exist yet, and again when we create it, but not afterwards.




Reply from 2011 (see above for updates of 2013 and 2015):

No, JavaScript does not have full access. The accessor syntax used is discussed in Section 11.1.5 of the specification and does not offer any template or something like that.

You could, of course, implement a function for this, but I assume that you probably do not want to use f = obj.prop("foo"); , not f = obj.foo; and obj.prop("foo", value); , not obj.foo = value; (which would be necessary for a function to handle unknown properties).

FWIW, the getter function (I was not worried about the setter logic) would look something like this:

 MyObject.prototype.prop = function(propName) { if (propName in this) { // This object or its prototype already has this property, // return the existing value. return this[propName]; } // ...Catch-all, deal with undefined property here... }; 

But then again, I can’t imagine that you really want to do this, because of how it changes the way you use the object.

+117
Oct 25 2018-11-15T00:
source share

In modern Javascript (FF4 +, IE9 +, Chrome 5+, Safari 5.1+, Opera 11.60+) there is Object.defineProperty . This MDN example explains very well how defineProperty works, and makes dynamic getters and setters possible.

Technically, this will not work on any dynamic request, as you are looking for, but if your actual getters and setters are determined, for example, by calling AJAX on a JSON-RPC server, for example, use this as follows:

 arrayOfNewProperties.forEach(function(property) { Object.defineProperty(myObject, property.name, { set: property.setter, get: property.getter }); }); 
+31
Feb 13 '12 at 18:15
source share

The following is an original approach to this problem:

 var obj = { emptyValue: null, get: function(prop){ if(typeof this[prop] == "undefined") return this.emptyValue; else return this[prop]; }, set: function(prop,value){ this[prop] = value; } } 

To use it, properties must be passed as strings. So here is an example of how it works:

 //To set a property obj.set('myProperty','myValue'); //To get a property var myVar = obj.get('myProperty'); 

Edit: An improved, more object-oriented approach based on what I suggested is as follows:

 function MyObject() { var emptyValue = null; var obj = {}; this.get = function(prop){ return (typeof obj[prop] == "undefined") ? emptyValue : obj[prop]; }; this.set = function(prop,value){ obj[prop] = value; }; } var newObj = new MyObject(); newObj.set('myProperty','MyValue'); alert(newObj.get('myProperty')); 

You can see how it works here .

+4
May 30 '14 at 10:23
source share
 var x={} var propName = 'value' var get = Function("return this['" + propName + "']") var set = Function("newValue", "this['" + propName + "'] = newValue") var handler = { 'get': get, 'set': set, enumerable: true, configurable: true } Object.defineProperty(x, propName, handler) 

it works for me

-2
Nov 28 '13 at 20:55
source share



All Articles