In JavaScript, accessor properties are methods that get or set the value of an object. For that, we use these two keywords: get - to define a getter method to get the property value. set - to define a setter method to set the property value.
A getter or setter can be very useful at times, but they only need to be used when their specific functionality is required - otherwise plain property access without a getter or setter can be just fine. A getter has use when you want to run some code every time a property is requested.
You can only have one getter or setter per name, on an object. (So you can have both one value getter and one value setter, but not two 'value' getters.) The only way to delete a getter or setter is to do: 'delete object[name];' Be aware, that this command is capable of deleting normal properties, getters and setters.
2013 and 2015 Update (see below for the original answer from 2011):
This changed as of the ES2015 (aka "ES6") specification: JavaScript now has proxies. Proxies let you create objects that are true proxies for (facades on) other objects. Here's a simple example that turns any property values that are strings to all caps on retrieval:
"use strict";
if (typeof Proxy == "undefined") {
throw new Error("This browser doesn't support Proxy");
}
let original = {
"foo": "bar"
};
let proxy = new Proxy(original, {
get(target, name, receiver) {
let rv = Reflect.get(target, name, receiver);
if (typeof rv === "string") {
rv = rv.toUpperCase();
}
return rv;
}
});
console.log(`original.foo = ${original.foo}`); // "original.foo = bar"
console.log(`proxy.foo = ${proxy.foo}`); // "proxy.foo = BAR"
Operations you don't override have their default behavior. In the above, all we override is get
, but there's a whole list of operations you can hook into.
In the get
handler function's arguments list:
target
is the object being proxied (original
, in our case).name
is (of course) the name of the property being retrieved, which is usually a string but could also be a Symbol.receiver
is the object that should be used as this
in the getter function if the property is an accessor rather than a data property. In the normal case this is the proxy or something that inherits from it, but it can be anything since the trap may be triggered by Reflect.get
.This lets you create an object with the catch-all getter and setter feature you want:
"use strict";
if (typeof Proxy == "undefined") {
throw new Error("This browser doesn't support Proxy");
}
let obj = new Proxy({}, {
get(target, name, receiver) {
if (!Reflect.has(target, name)) {
console.log("Getting non-existent property '" + name + "'");
return undefined;
}
return Reflect.get(target, name, receiver);
},
set(target, name, value, receiver) {
if (!Reflect.has(target, name)) {
console.log(`Setting non-existent property '${name}', initial value: ${value}`);
}
return Reflect.set(target, name, value, receiver);
}
});
console.log(`[before] obj.foo = ${obj.foo}`);
obj.foo = "bar";
console.log(`[after] obj.foo = ${obj.foo}`);
The output of the above is:
Getting non-existent property 'foo' [before] obj.foo = undefined Setting non-existent property 'foo', initial value: bar [after] obj.foo = bar
Note how we get the "non-existent" message when we try to retrieve foo
when it doesn't yet exist, and again when we create it, but not after that.
Answer from 2011 (see above for 2013 and 2015 updates):
No, JavaScript doesn't have a catch-all property feature. The accessor syntax you're using is covered in Section 11.1.5 of the spec, and doesn't offer any wildcard or something like that.
You could, of course, implement a function to do it, but I'm guessing you probably don't want to use f = obj.prop("foo");
rather than f = obj.foo;
and obj.prop("foo", value);
rather than obj.foo = value;
(which would be necessary for the function to handle unknown properties).
FWIW, the getter function (I didn't bother with 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 again, I can't imagine you'd really want to do that, because of how it changes how you use the object.
The following could be 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;
}
}
In order to use it the properties should 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 proposed is the following:
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 it working here.
Preface:
T.J. Crowder's answer mentions a Proxy
, which will be needed for a catch-all getter/setter for properties which don't exist, as the OP was asking for. Depending on what behavior is actually wanted with dynamic getters/setters, a Proxy
may not actually be necessary though; or, potentially, you may want to use a combination of a Proxy
with what I'll show you below.
(P.S. I have experimented with Proxy
thoroughly in Firefox on Linux recently and have found it to be very capable, but also somewhat confusing/difficult to work with and get right. More importantly, I have also found it to be quite slow (at least in relation to how optimized JavaScript tends to be nowadays) - I'm talking in the realm of deca-multiples slower.)
To implement dynamically created getters and setters specifically, you can use Object.defineProperty()
or Object.defineProperties()
. This is also quite fast.
The gist is that you can define a getter and/or setter on an object like so:
let obj = {};
let val = 0;
Object.defineProperty(obj, 'prop', { //<- This object is called a "property descriptor".
//Alternatively, use: `get() {}`
get: function() {
return val;
},
//Alternatively, use: `set(newValue) {}`
set: function(newValue) {
val = newValue;
}
});
//Calls the getter function.
console.log(obj.prop);
let copy = obj.prop;
//Etc.
//Calls the setter function.
obj.prop = 10;
++obj.prop;
//Etc.
Several things to note here:
value
property in the property descriptor (not shown above) simultaneously with get
and/or set
; from the docs:
Property descriptors present in objects come in two main flavors: data descriptors and accessor descriptors. A data descriptor is a property that has a value, which may or may not be writable. An accessor descriptor is a property described by a getter-setter pair of functions. A descriptor must be one of these two flavors; it cannot be both.
val
property outside of the Object.defineProperty()
call/property descriptor. This is standard behavior.writable
to true
in the property descriptor if you use get
or set
.configurable
and enumerable
, however, depending on what you're after; from the docs:
configurable
true
if and only if the type of this property descriptor may be changed and if the property may be deleted from the corresponding object.Defaults to false.
enumerable
true
if and only if this property shows up during enumeration of the properties on the corresponding object.Defaults to false.
On this note, these may also be of interest:
Object.getOwnPropertyNames(obj)
: gets all properties of an object, even non-enumerable ones (AFAIK this is the only way to do so!).Object.getOwnPropertyDescriptor(obj, prop)
: gets the property descriptor of an object, the object that was passed to Object.defineProperty()
above.obj.propertyIsEnumerable(prop);
: for an individual property on a specific object instance, call this function on the object instance to determine whether the specific property is enumerable or not.I was looking for something and I figured out on my own.
/*
This function takes an object and converts to a proxy object.
It also takes care of proxying nested objectsa and array.
*/
let getProxy = (original) => {
return new Proxy(original, {
get(target, name, receiver) {
let rv = Reflect.get(target, name, receiver);
return rv;
},
set(target, name, value, receiver) {
// Proxies new objects
if(typeof value === "object"){
value = getProxy(value);
}
return Reflect.set(target, name, value, receiver);
}
})
}
let first = {};
let proxy = getProxy(first);
/*
Here are the tests
*/
proxy.name={} // object
proxy.name.first={} // nested object
proxy.name.first.names=[] // nested array
proxy.name.first.names[0]={first:"vetri"} // nested array with an object
/*
Here are the serialised values
*/
console.log(JSON.stringify(first)) // {"name":{"first":{"names":[{"first":"vetri"}]}}}
console.log(JSON.stringify(proxy)) // {"name":{"first":{"names":[{"first":"vetri"}]}}}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With