I am aware of how to create getters and setters for properties whose names one already knows, by 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, my question is, is it possible to define sort of catch-all getters and setters like these? I.e., create getters and setters for any property name which isn't already defined.
The concept is possible in PHP using the __get() and __set() magic methods (see the PHP documentation for information on these), so I'm really asking is there a JavaScript equivalent to these?
Needless to say, I'd ideally like a solution that is cross-browser compatible.
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, and returns "missing" instead of undefined for a property that doesn't exist:
"use strict";
if (typeof Proxy == "undefined") {
throw new Error("This browser doesn't support Proxy");
}
let original = {
example: "value",
};
let proxy = new Proxy(original, {
get(target, name, receiver) {
if (Reflect.has(target, name)) {
let rv = Reflect.get(target, name, receiver);
if (typeof rv === "string") {
rv = rv.toUpperCase();
}
return rv;
}
return "missing";
}
});
console.log(`original.example = ${original.example}`); // "original.example = value"
console.log(`proxy.example = ${proxy.example}`); // "proxy.example = VALUE"
console.log(`proxy.unknown = ${proxy.unknown}`); // "proxy.unknown = missing"
original.example = "updated";
console.log(`original.example = ${original.example}`); // "original.example = updated"
console.log(`proxy.example = ${proxy.example}`); // "proxy.example = UPDATED"
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.example = ${obj.example}`);
obj.example = "value";
console.log(`[after] obj.example = ${obj.example}`);
The output of the above is:
Getting non-existent property 'example'
[before] obj.example = undefined
Setting non-existent property 'example', initial value: value
[after] obj.example = value
Note how we get the "non-existent" message when we try to retrieve example when it doesn't yet exist, and again when we create it, but not after that.
Answer from 2011 (obsoleted by the above, still relevant to environments limited to ES5 features like Internet Explorer):
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("example"); rather than f = obj.example; and obj.prop("example", value); rather than obj.example = 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.
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:
You cannot use the 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.
Thus, you'll note that I created a val property outside of the Object.defineProperty() call/property descriptor. This is standard behavior.
As per the error here, don't set writable to true in the property descriptor if you use get or set.
You might want to consider setting 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.
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.
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"}]}}}
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)
this works for me
Related
I'm creating a virtualized object that lazily builds its properties with a rules engine as a technique to skip calculating values that are never read. For this, I'm using a Proxy. It seems like proxies sort of serve double-duty for both access forwarding and virtualizing; in my case, I mostly care about the latter, not the former.
The problem I'm having has to do with trying to implement the getOwnPropertyDescriptor trap in the proxy. When doing so I get the error:
TypeError: 'getOwnPropertyDescriptor' on proxy: trap returned descriptor for property 'foo' that is incompatible with the existing property in the proxy target
Because I'm not actually forwarding requests to a wrapped object, I've been using an Object.freeze({}) as the first argument passed to new Proxy(...).
function createLazyRuleEvaluator(rulesEngine, settings) {
const dummyObj = Object.freeze({});
Object.preventExtensions(dummyObj);
const valueCache = new Map();
const handlers = {
get(baseValue, prop, target) {
if (valueCache.has(prop))
return valueCache.get(prop);
const value = rulesEngine.resolveValue(settings, prop);
valueCache.set(prop, value);
return value;
},
getOwnPropertyDescriptor(baseValue, prop) {
return !propIsInSettings(prop, settings) ? undefined : {
configurable: false,
enumerable: true,
get() {
return handlers.get(baseValue, prop, null);
}
};
},
};
return new Proxy(dummyObj, handlers);
}
// This throws
Object.getOwnPropertyDescriptor(createLazyRuleEvaluator(/*...*/), 'foo');
The proxy object is meant to resemble a object with read-only properties that can't be extended or have any other such fanciness. I tried using a frozen and non-extensible object, but I'm still told the property descriptor is incompatible.
When I try using a non-frozen object as the proxy target, then I get a different type error:
TypeError: 'getOwnPropertyDescriptor' on proxy: trap reported non-configurability for property 'foo' which is either non-existent or configurable in the proxy target
What am I doing wrong here? Is there any way I can have my proxy exhibit non-configurability?
You might want to consult the list of invariants for handler.getOwnPropertyDescriptor(), which unfortunately includes these:
A property cannot be reported as existent, if it does not exist as an own property of the target object and the target object is not extensible.
A property cannot be reported as non-configurable, if it does not exist as an own property of the target object or if it exists as a configurable own property of the target object.
Since your target is a frozen, empty object, the only valid return value for your handler.getOwnPropertyDescriptor() trap is undefined.
To address the underlying question though, lazily initialize your property descriptors when they're attempted to be accessed:
function createLazyRuleEvaluator(rulesEngine, settings) {
const dummyObj = Object.create(null);
const valueCache = new Map();
const handlers = {
get(target, prop) {
if (valueCache.has(prop))
return valueCache.get(prop);
const value = prop + '_value'; // rulesEngine.resolveValue(settings, prop)
valueCache.set(prop, value);
return value;
},
getOwnPropertyDescriptor(target, prop) {
const descriptor =
Reflect.getOwnPropertyDescriptor(target, prop) ||
{ value: handlers.get(target, prop) };
Object.defineProperty(target, prop, descriptor);
return descriptor;
},
};
return new Proxy(dummyObj, handlers);
}
console.log(Object.getOwnPropertyDescriptor(createLazyRuleEvaluator(/*...*/), 'foo'));
You can follow this pattern for each of the method traps to preempt unwanted mutations to your dummyObj by lazily initializing any properties accessed.
I am running into an issue where I am getting an error for properties that I've added to an object via Object.defineProperty.
The error in question.
Exception: RangeError: Maximum call stack size exceeded
Maybe (likely) my design is incorrect and I should be doing something differently. This is what I intend to do with the code below:
Create an object P via a factory function.
Pass a config object C to the factory to customise P.
Store C within P as a private object and get/set the values of C by attaching its properties to P via Object.defineProperty. C may be different for any given P.
The problem comes when I want to override the default get/set methods for some C.a
I do that as follows:
// Create P with a custom (non-default) get method.
let C = { a: 1, b: 2, c: 3 };
let P = factory.createObject(C);
const customGetA = function(object, property) {
return function() {
if(!object[property])
object[property] = ' ';
return object[property];
};
};
P.customgGetMethod('a', customGetA);
// Looking at object in the console reveals the error mentioned above.
let factory = (function() {
'use strict';
this.createObject = function(config) {
const product = {};
let C = config;
// Add default getters/setters to the product, referencing the properties of C.
for (const property in config) {
Object.defineProperty(product, property, {
get: function() {
return C[property];
},
set: function(value) {
C[property] = value;
},
configurable: true,
enumerable: true
});
}
product.customGetMethod = function(property, callback) {
// Get the property description.
let descriptor = Object.getOwnPropertyDescriptor(this, property);
// Assign the custom get method within the callback, binding its values via closure.
descriptor.get = callback(this, property);
// Redefine the property with the new get method.
Object.defineProperty(this, property, descriptor);
};
return product;
};
})();
In the end, I want a to be able to pass a custom data object into P and have it remain private, and dynamically generate get/set methods based off of that data so I don't have to get/set boiler plate for N-properites * M-products. This may not be the best design or implementation, but I am at a loss for how to do it another way.
Any alternatives or insight would be appreciated.
The getter function that customGetA creates in P.customgGetMethod('a', customGetA); is essentially
function() {
if(!product.a)
product.a = ' ';
return product.a;
}
When we compare that to the default getter created in the factory
function() {
return C.a;
}
we can see that the new one looks up the value in product, not the configuration C. And looking up a property in product evaluates its getter, which is the function we already are in, which recurses until it eventually overflows the stack...
I think you are looking for
// Assign the custom get method within the callback, binding its values via closure.
descriptor.get = callback(C, property);
// ^
to close over the internal configuration object.
Given an object like:
var box = { number: 20 }
I would like to tie a "before writing" Proxy (or equivalent) to it. This proxy would act as a middleware and perform a type-check.
For example, after doing box.number = "30" It would verify that typeof === "number". Since it is not, it would show an error.
Doing box.number = 30 would not trigger the proxy.
What I've tried:
This. Works only for undefined properties.
Watcher.JS The value gets written and then the middleware is executed (because it is a watcher). The middleware should execute first.
What I know that can be done:
I know that I can simply check the typeof of the variable beforehand. I am looking for a Proxy solution.
Custom defined functions (getters & setters). I would like to experiment with proxies, all properties have dynamic names.
In my opinion, and consdering your needs, get/set is a better approach, they are faster in execution time, and the code is easier to mantain.
GETTERS/SETTERS SOLUTION
Are arround since the arrival of ES5...
A getter is a method that gets the value of a specific property.
A setter is a method that sets the value of a specific property.
You can define getters and setters on any predefined core object or
user-defined object that supports the addition of new properties. The
syntax for defining getters and setters uses the object literal
syntax.
+info : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Working_with_Objects
var box = {
_number : 0,
get number(){ return this._number },
set number(value){
/* your verifications here */
if( typeof value !== 'number' ) throw new Error('Invalid input')
/* if validates , asign value */
this._number = value;
}
}
// test...
box.number = "1234"; // FAIL
box.number = 1234; // OK
box.number; // output = 1234
PROXY SOLUTION
Available since ES6. Probably not appropiate if performance is important for you. Use the GET/SET proxy traps, to obtain the same behavior as in the previous example.
// your original object...
var box = {};
// create a proxy linked to your original object
var boxProxy = new Proxy( box , {
get( obj, prop, receiver ){ return obj[prop] },
set( obj, prop, value){
/* check if the property trying to be written is 'number' */
if( prop === 'number' ){
/* your verifications here */
if( typeof value !== 'number' ) throw new Error('Invalid input')
/* if validates , asign value */
this._number = value;
}
}
});
// test...
boxProxy.number = "1234"; // FAIL
boxProxy.number = 1234; // OK
boxProxy.number; // output = 1234
In both cases, if you require the propery box._number to be private and hidden you may implement it, using closures.
Here is an example, you have a verification code, and a default value
var box =Object.create({},{
number: {
get: function() { return this._number || 0; },
set: function(value) {
if(typeof value!='number') throw new Error('error')
this._number=value
}
}
})
If you want to hide the private _number you can wrap in a function
var box=(function() {
var _number
var box =Object.create({},{
number: {
get: function() { return _number || 0; },
set: function(value) {
if(typeof value!='number') throw new Error('error')
_number=value
}
}
})
return box
})()
box.number=3
More info:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create
var myObject = {"myKey" : "myValue"}
typeof(myObject.myKey) returns `string`
myObject.myKey = "newValue"
console.log(myObject.myKey) prints newValue
This is the expected behavior. But, similar value writes do not work for document.cookie
typeof(document.cookie) returns `string`
But performing document.cookie = "value=123", appends to document.cookie string rather than set its value to value=123
So, how is assignment to document.cookie overridden?
document.cookie is a little magical, but depending on your browser constraints, you an use Object.defineProperty to define properties that have different get and set behavior.
For example:
var obj = {};
Object.defineProperty(obj, "data", {
get: function() {return this.val; },
set: function(val) { this.val = JSON.stringify(val); }
});
obj.data = {a:1}; // Set as an object...
console.log(obj.data) // but retrieve as string '{"a":1}'
For example, to do something similar to the cookie example, you could make a function like:
var mixinExtender = (function mixinExtender(target) {
var rawValue = {};
Object.defineProperty(target, "data", {
get: function() { return JSON.stringify(rawValue); },
set: function(val) {
for(var key in val) {
rawValue[key] = val[key];
}
}
});
})
This will mixin in a data property that will extend the setter value into a private object. The getter will return a serialized version of it. Then you could use it with:
var obj = {};
mixinExtender(obj);
obj.data = {a:1}; // Add "a" key
obj.data = {b:2}; // Add "b" key
console.log(obj.data) // > {"a":1,"b":2}
Browser-supplied host objects behave in ways that are not constrained by the semantics of the language. That is, document looks like a JavaScript object, but it's not. It's part of the runtime environment.
The JavaScript spec is written in terms of various internal "method" descriptions. Host objects like window and document have special versions of those internal methods. Thus, the runtime follows the spec as to how the = assignment process works, but the internal method [[Put]] is simply special.
By "internal" I mean those defined in ES5 8.6.2:
http://www.ecma-international.org/publications/standards/Ecma-262.htm
One can access the [[Class]] internal property by using
Object.prototype.toString(Object)
What are these properties for and are the accessible?
The specification does not claim to define a way to modify(p32-footer).
NOTE This specification defines no ECMAScript language operators or
built-in functions that permit a program to modify an object‘s
[[Class]] or [[Prototype]] internal properties or to change the value
of [[Extensible]] from false to true. Implementation specific
extensions that modify [[Class]], [[Prototype]] or [[Extensible]] must
not violate the invariants defined in the preceding paragraph.
Are they accessible?
Not quite, you can figure out what they return (based on their individual definitions §8.12) but you can't change how they work.
Here are ways to work out most of them (listed in §8.6.2).
For all examples, I'm assuming the object is stored as obj and property key is name, val is a value and descriptor is a property descriptor.
[[Prototype]], Object.getPrototypeOf(obj)
[[Class]], Object.prototype.toString.call(obj)
[[Extensible]], Object.isExtensible(obj)
[[Get]], obj[name]
[[GetOwnProperty]], Object.getOwnPropertyDescriptor(obj, name)
[[GetProperty]], Object.getOwnPropertyDescriptor(obj, name) combined with going up inheritance (Object.getPrototypeOf)
[[Put]], obj[name] = val
[[CanPut]], Look up getOwnPropertyDescriptor, if undefined is obj extensible (Object.isExtensible)? Otherwise, check writable or existance of a setter set
[[HasProperty]], name in obj
[[Delete]], delete obj.name
[[DefaultValue]], not sure about this one
[[DefineOwnProperty]], Object.defineProperty(obj, name, descriptor)
What are these properties for?
They're to do with the internal mechanics of how the JavaScript engine should work according to the specification (§8.12), and are referred to in algorithms (example).
ES6+
In ES6 we have access to Proxys, this means we can create objects and then wrap them with a Proxy to let us handle get, set, has, etc in a custom way
// have some object
let o = {};
// wrap with proxy defining a get handler and set handler
let p = new Proxy(o, {
get(t, n) {
console.log(t, n, t[n]);
return t[n];
},
set(t, n, v) {
t[n] = +v;
return true;
}
});
// now accessing via proxy
p.foo; // undefined
// get handler logs Object o, "foo", undefined (this happens before .foo returns)
p.foo = '123'; // uses handler to sets on `o`
p.foo; // 123, notice value is Number due to set handler
// get handler logs Object o, "foo", 123 (this happens before .foo returns)
Yes,
They are accessible:
Object.defineProperty({}, 'key', {
get: function(){
console.log('GOT %s', 'key');
return someValue;
},
set: function(value){
console.log('SET %s', 'key');
this[key] = value;
}
});
Which is equivalent to:
var object = {
_name: '',
get name(){ return this._name; },
set name(value){ this._name = value; }
};
However, I don't yet know how to access [[Put]] & [[Delete]] -- please elaborate if you do.