Can Object.defineProperty only modify the setter? - javascript

I want my object to have a field that when read returns the fields value, and when a val is written to the field, I want to modify the val before writing it. My current code is this:
function Cat(lives) {
var self = this;
var privateLives;
Object.defineProperty(self, 'publicLives', {
get: function() {return privateLives;},
set: function(val) {privateLives = 7 * val;}
});
}
Is there a way to do this without making a private variable? Ideally I would simply have the setter be this:
function(val) {self.publicLives = 7 * val;}
but that causes an overflow as the setter calls itself. Is there some way to make so it just doesn't loop the setter (so only assignment outside the setter's scope calls the setter and assignment in the setter just does normal assignment)? If that's possible, I wouldn't need to explicitly define a getter either as the setter writes to a public field.

No, this is not possible - a property can only be either a data property or an accessor property, not both. Of course you don't necessarily need to store the value in a private variable from your setter, you can use a different property or a property on a different object (like in #Oriol's proxy) as well. If you want to avoid private variables, "private" properties are the standard approach:
function Cat(lives) {
this.publicLives = lives;
}
Object.defineProperty(Cat.prototype, 'publicLives', {
get: function() {return this._privateLives;},
set: function(val) { this._privateLives = 7 * val;}
});
But you can also do some tricky things and hide the "private variable" by using a constant getter function that is repeatedly redefined:
Object.defineProperty(Cat.prototype, 'publicLives', {
set: function setter(val) {
val *= 7;
Object.defineProperty(this, 'publicLives', {
get: function() { return val; }
set: setter,
configurable: true
});
}
});

In ES6, an alternative would be using a Proxy object with a [[Set]] trap:
function Cat(lives) {
return new Proxy(this, {
set: function(target, prop, val) {
target[prop] = val;
if (prop === 'publicLives') target[prop] *= 7;
return true;
}
});
}

Related

create object function that generate properties to its caller object

I want to create a function inside an object. I need this function to generate setters and getters
for the properties of the caller object without generating getters or setters for the property of the function value.
I reached for something like this. But It gives me RangeError Maximum call stack size exceeded.
function Emp() {
return {
name: "Mohamed",
id: "5",
getSetGen: function() {
for (var i in this) {
if (typeof this[i] !== 'function') {
(function(j) {
Object.defineProperty(this, j, {
get: function() {
return this[j];
},
set: function(val) {
this[j] = val
}
})
})(i);
}
}
}
}
}
I want to apply getSetGen() to var user = { name:”Ali”,age:10} for example.
Is there any possible solution? Thanks in advance.
Edit:
This is the text that describes what I need ...
Create your own custom object that has getSetGen as
function value, this function should generate setters and getters
for the properties of the caller object
This object may have description property of string value if
needed
Let any other created object can use this function property to
generate getters and setters for his own properties
Avoid generating getters or setters for property of function
value
In trying to solve this I reworked some of your code, but I think this does basically what you're looking for, and you can tweak it to your preferences.
(I think the issue was that the new accessor property had the same name as the existing data property. See comments in the code for further explanation.)
const
baseObj = getBaseObj(),
user = { name: "Ali", age: 10 };
baseObj.addAccessorsForAllDataProps.call(user);
console.log("\nsetting age to 11...");
user.age = 11;
console.log("\nretrieving user age...");
console.log(user.age);
function getBaseObj(){
return {
name: "Mohamed",
id: 5,
addAccessorsForAllDataProps(){
for(let originalPropName in this){
if(!isFunction(this[originalPropName])){
// Renames the data property with an underscore
const dataPropName = `_${originalPropName}`
// Binds existing value to renamed data property
this[dataPropName] = this[originalPropName];
// Passes originalPropName to be used as accessorPropName
addAccessors(this, originalPropName, dataPropName);
}
}
// Accessor prop can't have same name as data prop
// (I think your stack overflow happened b/c getter got called infinitely)
function addAccessors(obj, accessorPropName, dataPropName){
Object.defineProperty(obj, accessorPropName, {
get: function(){
console.log("(getter invoked)"); // Just proving accessors get called
return obj[dataPropName];
},
set: function(val){
console.log("(setter invoked)");
obj[dataPropName] = val;
}
});
};
}
}
}
function isFunction(val) {
return val && {}.toString.call(val) === '[object Function]';
}

JavaScript Proxy Setter doesn't make a second Proxy call

I have the following code that is using Proxy for Class setter. In my example I am tracking specific variable to update some other variables. My Setter is writing a log of all changes to Console. However if I try to modify a variable from a setter itself, variable gas modified, but the Proxy isn't called. Is it by design to avoid looping? Or am I missing something?
class darthVader {
constructor() {
return new Proxy(this, {
set(obj, prop, value) {
console.log(`Setting ${prop} to ${value}`)
obj[prop] = value
return true
}
})
}
set resistance(val) {
this._resistance= val
this.darkSide = false
}
get resistance() { return this._R2D2 }
}
let newHero = new darthVader()
newHero.resistance = 11
console.log(newHero.darkSide)
The problem is that your trap just runs obj[prop] = value, which sets a property on the target obj not on the proxy. What you should do instead is to use the Reflect.set method that provides the default implementation for the set trap, and expects an optional receiver argument. This receiver is the object that setters will be evaluated against, and you should pass the receiver argument of the set trap (which will refer to the newHero proxy that you assigned resistance to).
class DarthVader {
set resistance(val) {
this._resistance= val
this.darkSide = false
}
get resistance() { return this._R2D2 }
}
let newHero = new Proxy(new DarthVader, {
set(target, prop, value, receiver) {
console.log(`Setting ${prop} to ${value}`)
return Reflect.set(target, prop, value, receiver)
// ^^^^^^^^^^^
// obj[prop] = value
}
});
newHero.resistance = 11
console.log(newHero.darkSide)
The obj inside the set method refers to what this is when you do return new Proxy(this, and that object is not a proxy, but the darthVader instance itself - the one that's in the process of being created by the darthVader constructor. So, when you assign to a property of obj, you're putting a property directly on the darthVader instance, rather than on the proxy instance (which is the newHero). So, the proxy method doesn't get called.
If you wanted to recursively invoke the proxy, you could define it (let's say, as the variable name proxy) before returning it from the constructor, and then reference proxy inside the set method, but given the current logic, this results in a stack overflow because you'd be continually calling the proxy's setter:
class darthVader {
constructor() {
const proxy = new Proxy(this, {
set(obj, prop, value) {
console.log(`Setting ${prop} to ${value}`)
proxy[prop] = value
return true
}
})
return proxy;
}
set resistance(val) {
this._resistance = val
this.darkSide = false
}
get resistance() {
return this._R2D2
}
}
let newHero = new darthVader()
newHero.resistance = 11
console.log(newHero.darkSide)

Error adding ownProperty objects to Object's prototype

I was adding some properties to objects using Object.defineProperty. It works normally when I add to the property of the object. But when I added it to the prototype of the object, I got a too much recursion error.
var obj = {};
Object.defineProperty(obj.__proto__, 'key', {
enumerable: true,
get: () => {return key},
set: (value) => {
if (typeof(value) == 'number'){
key = 2*value;
}
}
});
obj.key = 3;
console.log(obj.key);
Why can't objects in the prototype be added this way?
This is because by defining the property on the Object's prototype every object gets that getter / setter. And everything in JS is an Object so this means window is also going to have a key setter / getter.
Inside your setter / getter functions you just use key. Since you did not explicitly define a key variable it is going to use the one defined on window. This is what is causing the recursion.
You do obj.key = 3, which calls the setter which does, window.key = 2*value, which calls the setter, which does window.key = 2*value. So on and so forth till you reach maximum call stack.
Don't add something onto the prototype unless you are wanting it on every instance.
And since you are using arrow functions there is no this bound to the setter / getter calls and therefore you have no way of knowing which instance object you are interacting with.
Instead do not extend Object's prototype make one of your own, and set / get to the right variable / property.
For instance use actual constructor functions, and change arrow functions to regular function expressions. This will make it so the setter / getter can get a instance reference to the object. You will then need some way of keeping track of which key belongs to which instance. If not needing to hide these variables you could simply use prefixed named property, like: this._key = 2*value; Otherwise you can use WeakMaps if you want to keep them private.
function YourClass(){};
(function(){
var keyMap = new WeakMap();
Object.defineProperty(YourClass.prototype, 'key', {
enumerable: true,
get: function(){
return keyMap.get(this);
},
set: function(value){
if(typeof(value) == "number"){
keyMap.set(this,2*value);
}
}
});
})();
obj = new YourClass();
obj.key = 3;
console.log(obj.key);
function secondClass(){}
//make secondClass inherit from YourClass
secondClass.prototype = Object.create(
YourClass.prototype,
{
"constructor": {
configurable: true,
enumerable: false,
writable: true,
value: secondClass
}
});
var obj2 = new secondClass();
obj2.key = 16;
console.log(obj2.key);
Also since you seem ok with using ES6 you could just use Classes which have a set / get syntax
class YourClass {
constructor(){
this._key = null;
}
set key(value){
this._key = 2*value;
}
get key(){
return this._key;
}
}
class SecondClass extends YourClass {
constructor(){
super();
}
}
var obj = new SecondClass();
obj.key = 17;
console.log(obj.key);

ECMAscript 6: watch changes to class properties

Let's say I have a class defined as follows:
class MyClass {
constructor (a) {
this.a = a;
}
_onPropertyChanged() {
// do something
}
}
Whenever the property "a" of an instance of MyClass changes, I want to trigger the _onPropertyChanged method on that instance.
What is the best (most performant) way to achieve this using ECMAscript 6?
There's no 'best' way, and the actual approach always depends on the final goal.
In its simplistic (and performant enough) form it is:
class MyClass {
constructor (a) {
this.a = a;
}
get a() {
return this._a;
}
set a(val) {
this._a = val;
this._onPropertyChanged('a', val);
}
_onPropertyChanged(propName, val) {
// do something
}
}
This is a simple example of what you can do for this situation and for a single property.
class MyClass {
constructor (a) {
this._a = a;
}
set a(value) {
let hasChanged = (this._a !== value);
this._a = value;
//Assumes value is primitive. Customize for objects
if(hasChanged) this._onPropertyChanged();
}
_onPropertyChanged() {
// do something
}
}
The easiest thing is to define a setter and a getter for the property like the previous comment said. However, it won't work if you already have a defined setter.
Another way is a non-standard Object.prototype.watch, which only works natively in Gecko. If you want to bring its support to most other browsers, you can use a small but powerful Andrea Giammarchi's library.
The best way is redefine filed/property, but in this case u have some pit falls, like 'find owner of this prop'. U can use this solution for totally-safe property redefining.
The main idea is redefine property if exists:
Object.defineProperty(target, name, {
get() { return get(ownPropertyDescriptor.get.call(target)); },
set(val) {
let _val = set.call(this, val);
if (_val != undefined)
ownPropertyDescriptor.set.call(target, _val);
},
configurable: true
});
Or create new property based on symbol type:
var indexer = Symbol(name);
target[indexer] = target[name];
Object.defineProperty(target, name, {
get() {
var val = target[indexer];
var _val = get(val);
return _val;
},
set(val) {
let _val = set.call(this, val);
arget[indexer] = set(_val);
},
configurable: true
});
Full method is part of complex solution for property-redefining.

Programmatically defining Javascript properties in the object constructor

I have a Javascript constructor with an object containing all of its fields, and functions that read/write to them:
function myObj(){
var _settingsStore {
setting1: 1, //default value
setting2: 2, //default value
}
//gets/sets _settingsStore.settingName, does other stuff
function _genericSet(settingName, settingValue){}
function _genericGet(settingName){}
I'd like to programmatically create properties around each of the fields:
for (var _setting in _settingsStore){
var _str = _setting.toString();
Object.defineProperties(this, {
_str: {
get: function() {return _genericGet(_str)},
set: function(value) {return _genericSet(_str, value)}
}
})
}
My intent is for _str to be purely a variable, and that myObj.setting1 and myObj.setting2 should be defined. Instead, the loop attempts to define myObj._str twice:
Uncaught TypeError: Cannot redefine property: _str
What's going on? And is there a way to get my desired behavior?
EDIT: To clarify, this is what I effectively want to happen, just unrolled outside of the loop:
Object.defineProperties(this, {
settings1: {
get: function() {return _genericGet("settings1")},
set: function(value) {return _genericSet("settings1", value)}
},
settings2: {
get: function() {return _genericGet("settings2")},
set: function(value) {return _genericSet("settings2", value)}
},
//and so on, if there are more of them
})
I'm starting over. If you just want to use setters and getters that do some extra work, and each object has its on store hidden in the constructor, and you wanted to reuse some generic functions, you could do this:
function myObj(){
var _settingsStore = {
setting1: 1, //default value
setting2: 2, //default value
}
for (var _setting in _settingsStore){
makeGetSet(this, _setting, _settingStore);
}
}
function makeGetSet(obj, key, store) {
Object.defineProperty(obj, key, {
get: function() { return _genericGet(key, store)}
set: function(val) {_genericSet(key, val, store)}
})
}
function _genericGet(key, store){
var val = store[key];
// manipulate the val
return val
}
function _genericSet(key, val, store){
// manipulate the new val
store[key] = val
}
I really don't know what sort of manipulations you're doing when getting/setting, so some of this could maybe be shortened.
Also, you could make the _settingsStore generic, and just use the variable scope for the value storage since you need that anyway.
You're misunderstanding object literals.
{ _str: blah } is an object with a property named _str; it doesn't use the value of the _str variable.
Therefore, you were actually defining the same name every time.
Since you didn't set configurable: true, that threw an error.
Instead, you should call Object.defineProperty (singular), which takes a string followed by a property name.
(alternatively, you could build an object to pass to defineProperties and give it the correct name using indexer notation)
However, your code still won't work correctly, since all of the callbacks will close over the same variable (Javascript does not have block scope).
To solve that, you need to wrap each iteration in a separate function.

Categories

Resources