Is there any way to define a pair that works for both obj.prop
and obj['prop']
that doesn't require writing them for every property in a class?
Two options for you:
In the constructor, build the accessors in a loop over an array of the names of the properties you want to have accessors. This is creating them for each property, but not writing them for each property. :-)
Or use a Proxy
, but you may not want the overhead. With a Proxy
object, you can intercept the get
and set
actions regardless of what property is being gotten/set.
But in both cases, you'll need a place to put the actual values the accessors access. That could be a WeakMap
keyed by Holder
instances, or a private property (either truly private or pseudo-private), or something else.
Here's a quick sketch of #1 with a WeakMap
:
const holderData = new WeakMap();
class Holder {
constructor(def = "the door") {
// Create an object to hold the values for this instance
holderData.set(this, Object.create(null));
this.def = def;
}
}
for (const name of ["def", "x", "y", "z"]) {
Object.defineProperty(Holder.prototype, name, {
get() {
console.log(`(Getting "${name}")`);
return holderData.get(this)[name];
},
set(value) {
console.log(`(Setting "${name}" to "${value}")`);
holderData.get(this)[name] = value;
}
});
}
const h = new Holder();
console.log(`h.def = ${h.def}`);
console.log(`h["def"] = ${h["def"]}`);
h.x = "ecks";
console.log(`h.x = ${h.x}`);
When a Holder
instance is no longer reachable, it becomes eligible for garbage collection, and the same happens to the object in the WeakMap
.
Here's a quick sketch of #2, also with a WeakMap
although you could just use the target object (I think when I wrote this I hadn't had enough coffee yet):
const holderData = new WeakMap();
class Holder {
constructor(def = "the door") {
// Create an object to hold the values for this instance
holderData.set(this, Object.create(null));
// Create the proxy
const proxy = new Proxy(this, {
get(target, propKey, receiver) {
if (typeof propKey === "string") { // Or whatever check you want for
// the properties you want to handle
console.log(`(Getting ${propKey.toString()})`);
return holderData.get(target)[propKey];
}
// Default handling
return Reflect.get(target, propKey, receiver);
},
set(target, propKey, value, receiver) {
if (typeof propKey === "string") { // Or whatever
console.log(`(Setting "${propKey.toString()}" to "${value}")`);
holderData.get(target)[propKey] = value;
return true;
}
// Default handling
return Reflect.set(target, propKey, receiver, value);
}
});
proxy.def = def;
return proxy;
}
}
const h = new Holder();
console.log(`h.def = ${h.def}`);
console.log(`h["def"] = ${h["def"]}`);
h.x = "ecks";
console.log(`h.x = ${h.x}`);
Here's that same thing using the target object to hold the values:
class Holder {
constructor(def = "the door") {
this.def = def;
// Return the proxy
return new Proxy(this, {
get(target, propKey, receiver) {
if (typeof propKey === "string") { // Or whatever check you want for
// the properties you want to handle
console.log(`(Getting ${propKey.toString()})`);
return target[propKey];
}
// Default handling
return Reflect.get(target, propKey, receiver);
},
set(target, propKey, value, receiver) {
if (typeof propKey === "string") { // Or whatever
console.log(`(Setting "${propKey.toString()}" to "${value}")`);
target[propKey] = value;
return true;
}
// Default handling
return Reflect.set(target, propKey, receiver, value);
}
});
}
}
const h = new Holder();
console.log(`h.def = ${h.def}`);
console.log(`h["def"] = ${h["def"]}`);
h.x = "ecks";
console.log(`h.x = ${h.x}`);