Setters/Getters for a nested Object - javascript

I know how to set Custom Setters & Getters in Javascript using Object.defineProperty
My intention with the following code snippet is to be able to hit the setter function whenever a property nested value inside the globalProject object is modified.
var ClassA = function () {
this.globalProject = {
a: "DEFAULT VALUE",
b: null
};
this.LastSavedProject = {};
};
Object.defineProperty(ClassA.prototype, 'globalProject', {
get: function () {
console.log("OVERRIDE");
return this._flag;
},
set: function (val) {
console.log("EDITING A DEEP VALUE")
this._flag = val;
},
});
var obj = new ClassA();
obj.globalProject.a = "HELLO WORLD" //Should get "EDITING A DEEP VALUE" in the console.
I imagine what is happening is that the getter method is being called and returning a reference to an object I want to modify. Because of that the setter is never being called since I am modifying a reference to a nested value and not the property I have a setter on.
Can anyone help me sort out this issue? Here's a fiddle: http://jsfiddle.net/z7paqnza/

When you execute obj.globalProject.a = "HELLO WORLD", you simply see "OVERRIDE" in the console because you are getting the value of obj.globalProject and setting the value of its data member a.
You do not see "EDITING A DEEP VALUE" in the console because you never set globalProject to refer to a different object, you simply changed one of the underlying object's data members. If you executed something like obj.globalProject = null, however, you would see "EDITING A DEEP VALUE" printed to the console, for you would have changed what object obj.globalProject refers to. See this jsfiddle: http://jsfiddle.net/z7paqnza/1/

What #PaulDapolito said is exactly correct. We are not calling the setter of globalObject when deep object is set. I have updated the code to add setters for deep object and now it calls the inner object setters. Here is the jsfiddle: http://jsfiddle.net/sjLLbqLc/4/
For this particular question, we can do the insert operation inside the GlobalProject class setters.
I am late to the party, but I hope this helps someone who lands up here in search of it.
var ClassA = function() {
this.globalProject = new GlobalProject(); // this sets global object
// you can initilaize your default values here to the global object. But you need the new GlobalProject() which will create the object with setters and getters.
};
Object.defineProperty(ClassA.prototype, 'globalProject', {
get: function() {
return this._flag;
},
set: function(val) {
console.log("Setting global object");
this._flag = val;
},
});
var GlobalProject = function() {
Object.defineProperty(GlobalProject.prototype, "a", {
get: function() {
return this._a;
},
set: function(value) {
console.log("Seting deep object");
this._a = value;
}
});
Object.defineProperty(GlobalProject.prototype, "b", {
get: function() {
return this._b;
},
set: function(value) {
this._b = value;
}
});
};
var obj = new ClassA();
obj.globalProject.a = "HELLO WORLD";// this sets the inner object

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]';
}

Does Javascript writable descriptor prevent changes on instances?

Answers (please read them below, their respective authors provided valuable insights):
"writable: false" prevents assigning a new value, but
Object.defineProperty is not an assignement operation and therefore
ignores the value of "writable"
property attributes
are inherited, therefore a property will remain non writable on every
subclasses/instances until one subclass (or instance of subclass) changes the value of "writable" back to true for itself
Question:
MDN documentation concerning the property "writable" descriptor states:
writable
true if and only if the value associated with the property may be changed with an assignment operator.
Defaults to false.
The official ECMA-262 6th edition more or less states the same.
The meaning is clear but, to my understanding, it was limited to the original property (i.e. the property on that specific object)
However, please consider the following example (JSFiddle):
//works as expected, overloading complete
var Parent = function() {};
Object.defineProperty(Parent.prototype, "answer", {
value: function() { return 42; }
});
var Child = function() {};
Child.prototype = Object.create(Parent.prototype, {
answer: {
value: function() { return 0; }
}
});
var test1 = new Parent();
console.log(test1.answer()); //42
var test2 = new Child();
console.log(test2.answer()); //0
//does not work as expected
var Parent2 = function() {};
Object.defineProperty(Parent2.prototype, "answer", {
value: function() { return 42; }
});
var Child2 = function() {};
Child2.prototype = Object.create(Parent2.prototype);
test3 = new Parent2();
console.log(test3.answer()); //42
test4 = new Child2();
test4.answer = function() { return 0; };
console.log(test4.answer()); //42
Following this example, we see that, although the property is not writable, it can be overloaded on the prototype of a subclass (test2), as I would expect.
However, when trying to overload the method on an instance of a subclass (test4), it fails silently. I would have expected it to work just as test2. The same happens when trying to overload the property on an instance of Parent.
The same thing occurs in both NodeJS and JSFiddle and, under some conditions, overloading on the instance throws a TypeError concerning the readonly nature of the property.
Could you please confirm to me that this is the expected behaviour ? If so, what is the explanation ?
Yes, this is expected behaviour.
it fails silently.
Not exactly. Or: Only in sloppy mode. If you "use strict" mode, you'll get an
Error { message: "Invalid assignment in strict mode", … }
on the line test4.answer = function() { return 0; };
it can be overloaded on the prototype of a subclass (test2), but not an instance of a subclass (test4)
This has nothing to do with instances vs. prototypes. What you didn't notice is that you're using different ways to create the overloading property:
an assignment to a property that is inherited and non-writable fails
an Object.defineProperty call just creates a new property, unless the object is non-extensible
You can do the same for your instance:
Object.defineProperty(test4, "answer", {
value: function() { return 42; }
});
You cannot write to an instance property if its prototype defines that property as unwritable (and the object instance doesn't have a descriptor) because the set operation goes up to the parent (prototype) to check if it can write, even though it would write to the child (instance). See EcmaScript-262 Section 9.1.9 1.c.i
4. If ownDesc is undefined, then
a. Let parent be O.[[GetPrototypeOf]]().
b. ReturnIfAbrupt(parent).
c. If parent is not null, then
i. Return parent.[[Set]](P, V, Receiver).
However, if you are trying to get around that, you can set the descriptor of the instance itself.
var proto = Object.defineProperties({}, {
foo: {
value: "a",
writable: false, // read-only
configurable: true // explained later
}
});
var instance = Object.create(proto);
Object.defineProperty(instance, "foo", {writable: true});
instance.foo // "b"
I've taken your example code and structured all the possible ways to change the possible outcome: https://jsfiddle.net/s7wdmqdv/1/
var Parent = function() {};
Object.defineProperty(Parent.prototype,"type", {
value: function() { return 'Parent'; }
});
var oParent = new Parent();
console.log('parent', oParent.type()); // Parent
var Child1 = function() {};
Child1.prototype = Object.create(Parent.prototype, {
type: {
value: function() { return 'Child1'; }
}
});
var oChild1 = new Child1();
console.log('child1', oChild1.type()); // Child1
var Child2 = function() {};
Child2.prototype = Object.create(Parent.prototype);
Object.defineProperty(Child2.prototype, 'type', {
value: function() { return 'Child2'; }
});
var oChild2 = new Child2();
console.log('child2', oChild2.type()); // Child2
var Child3 = function() {};
Child3.prototype = Object.create(Parent.prototype);
var oChild3 = new Child3();
oChild3.type = function() { return 'Child3'; };
console.log('child3', oChild3.type()); // Parent
var Child4 = function() {};
Child4.prototype = Object.create(Parent.prototype);
Child4.prototype.type = function() { return 'Child4'; };
var oChild4 = new Child4();
console.log('child4', oChild4.type()); // Parent
Object.defineProperty(Parent.prototype,"type", {
value: function() { return 'Parent2'; }
});
var oParent2 = new Parent();
console.log('parent2',oParent2.type());
When you use Object.create(...) to clone the prototype, the original descriptors are still attached higher up the prototype chain.
When assigning something to child.answer = 10 it will use Child.prototype.answer.writable. If that doesn't exist it will try Child.prototype.constructor.prototype.answer.writable if it does.
However, if you try Object.defineProperty(Child.prototype, ...) it won't check the prototype chain. It will test if it is defined on Child.prototype. if it's not defined, it will define it then.

Javascript pass changing object

I don't know if it's even possible but we made a statemachine in Javascript.
We have the variable
currentState = stateA
I'd like to pass the currentState as a parameter into an other object, that calls it's methods.
The state changes, so we have
currentState = stateB
The object that uses the currentState call's the stateA method, not the stateB
Is it possible to let it change with it? (pass by reference???)
Not possible. But you can get around it easily. For example:
var StateMachine = (function() {
var _state= null;
return {
setState: function(state) {
_state = state;
},
getState: function() {
return _state;
}
}
})();
var stateA = {
hello: function() {
alert("state A");
}
};
var stateB = {
hello: function() {
alert("state B");
}
}
setState(stateA);
getState().hello(); // prints "state A";
setState(stateB);
getState().hello(); // prints "state B";
This way you make sure the state is only changed via the getter/setter functions.
You can change it if you wrap it in another object. Just as a very rough draft to get you started, you can try this sample:
var StateManager = { currentState: 'stateA' };
function doStuff(sm) {
console.log(sm.currentState); // stateA
changeState(sm);
console.log(sm.currentState); // stateB
}
function changeState(sm) {
sm.currentState = 'stateB';
}
doStuff(StateManager);
Just for the sake of it, here is an idea of what happens:
var o = {a:1}; // o points to an object
f(o); // the pointer is passed to the function
function f(obj) { // obj points to the object above
obj.a = 2; // actual object changed
obj = null; // obj no longer points to that object, but the object remains
}
console.log(o); // o still points to the object
I would say it's possible in some extent.
It all depends on browser support of Ecma script 5.
Have a look at the Object.defineProperty in spec. There you can define get and set methods for your property.
For a more compatible way of doing this use a closure where you define a private variable that you later access with your own defined getState and setState methods.

Javascript: Calling object methods within that object

What is the best design pattern for achieving the following (which doesn't work)?
var obj = (function() {
// code defining private variables and methods
var _obj = {
property: value,
method1: function() {
// do stuff
},
method2: function() {
// use property
var prop = _obj.property; // obviously doesn't work
// call method1
obj.method1(); // "obj" not finished being defined yet!
}
};
// obviously now I could do...
var prop = _obj.property;
return _obj;
})();
// and I could now do...
obj.method1();
A variation which I think should work is
var obj = (function() {
var property = value,
method1 = function() {
// do stuff
},
method2 = function() {
// use property
var prop = property;
// call method1
method1();
},
_obj = {
property: property,
method1: method1,
method2: method2
};
return _obj;
})();
Similarly, how does it work for objects meant to be created with the new operator? Within the constructor function itself you can write this.method(). But what if you want to keep the constructor small, only defining those things which will likely be customized upon creation, and then defining the rest in the prototype? (This seems to be the common pattern.) Can the properties / methods within the prototype interact in any way?
var MyObj = function(name) {
this.name = name;
};
var obj = new MyObj('Bob');
MyObj.prototype = {
called_often: function() {
// lots more code than just the following
return document.getElementById('someID').value;
},
global_default: 'value', // can be changed, so need to pull value when run
does_stuff: function(value) {
var str = global_default + value, // can't access global_default on its own
input = MyObj.called_often(), // doesn't work; MyObj.prototype.called_often() DOES
name = this.name; // 'this' used in the prototype doesn't work
// even within a created object
return name + input + str;
}
};
I'm sure there's better ways to achieve my result whenever I run into this problem. This code isn't situation specific and just illustrates the general problem. So you won't be able to give me an alternative for those specific situations I run into. But maybe you can help my overall thinking.
Well, from your first example:
var _obj = {
property: value,
method1: function() {
// do stuff
},
method2: function() {
// use property
var prop = this.property;
// call method1
this.method1();
}
};
That's what the this value is for.
Now, what you cannot do is refer to a property of an "under construction" object from elsewhere in the object literal syntax. (It's hard to give an example because it's just not syntactically possible.) In cases where you want to do that, you do need one or more separate assignment statements.
Guess what? You are making simple stuff complex. Pointy's answer is good, but the prototype way is better for several reasons. That's why I am describing (rather, making corrections in) the last method. Check this fiddle.
var MyObj = function(name) {
this.name = name;
};
MyObj.prototype = {
called_often: function() {
// lots more code than just the following
return 'VALUE'; //document.getElementById('someID').value;
},
global_default: 'value', // can be changed, so need to pull value when run
does_stuff: function(value) {
var str = this.global_default + value, // can't access global_default on its own
input = this.called_often(), // doesn't work; MyObj.prototype.called_often() DOES
name = this.name; // 'this' used in the prototype doesn't work
// even within a created object
return name + input + str;
}
};
var obj = new MyObj('Bob');

javascript class inherit from Function class

I like that in javascript, I can create a function, and then add further methods and attributes to that function
myInstance = function() {return 5}
myInstance.attr = 10
I would like to create a class to generate these objects. I assume I have to inherit from the Function base class.
In other words, I would like to:
var myInstance = new myFunctionClass()
var x = myInstance()
// x == 5
But I don't know how to create the myFunctionClass. I have tried the following, but it does not work:
var myFunctionClass = function() {Function.call(this, "return 5")}
myFunctionClass.prototype = new Function()
myInstance = new myFunctionClass()
myInstance()
// I would hope this would return 5, but instead I get
// TypeError: Property 'myInstance' of object #<Object> is not a function
I also tried the more complicated (and more proper?) inheritance method found here: How to "properly" create a custom object in JavaScript?, with no more luck. I have also tried using the util.inherits(myFunctionClass, Function) found in node.js. Still no luck
I have exhausted Google, and therefore feel that I must be missing something fundamental or obvious. Help would be greatly appreciated.
Your trying to inherit from Function. This is a right pain to do. I suggest you do the following instead
Live Example
var Proto = Object.create(Function.prototype);
Object.extend(Proto, {
constructor: function (d) {
console.log("construct, argument : ", d);
this.d = d;
// this is your constructor logic
},
call: function () {
console.log("call", this.d);
// this get's called when you invoke the "function" that is the instance
return "from call";
},
method: function () {
console.log("method");
// some method
return "return from method";
},
// some attr
attr: 42
});
You want to create a prototype object that forms the basis of your "class". It has your generic methods/attributes. It also has a constructor that gets invoked on object construction and a call method that gets invoked when you call the function
var functionFactory = function (proto) {
return function () {
var f = function () {
return f.call.apply(f, arguments);
};
Object.keys(proto).forEach(function (key) {
f[key] = proto[key];
});
f.constructor.apply(f, arguments);
return f;
}
}
A function factory takes a prototype object and returns a factory for it. The returned function when called will give you a new function object that "inherits" from your prototype object.
var protoFactory = functionFactory(proto);
var instance = protoFactory();
Here you create your factory and then create your instance.
However this isn't proper prototypical OO. we are just shallow copying properties of a prototype into a new object. So changes to the prototype will not reflect back to the original object.
If you want real prototypical OO then you need to use a hack.
var f = function () {
// your logic here
};
f.__proto__ = Proto;
Notice how we use the non-standard deprecated .__proto__ and we are mutating the value of [[Prototype]] at run-time which is considered evil.
JS does not allow a constructor to return a function, even though functions are objects. So you cant have an instantiation of a prototype that is itself executable. (Am I right in this? please correct if I'm not, it's an interesting question).
Though you could do a factory function:
var makeCoolFunc = function() {
var f = function() { return 5 };
f.a = 123;
f.b = 'hell yes!'
return f;
};
var func = makeCoolFunc();
var x = func();
You can extend Function and pass the wanted function body as String to the super constructor. The context of the function can be accessed with arguments.callee.
Example for an observable Attribute class:
export default class Attribute extends Function {
constructor(defaultValue){
super("value", "return arguments.callee.apply(arguments);");
this.value = defaultValue;
this.defaultValue = defaultValue;
this.changeListeners = [];
}
apply([value]){
if(value!==undefined){
if(value!==this.value){
var oldValue = this.value;
this.value=value;
this.changeListeners.every((changeListener)=>changeListener(oldValue, value));
}
}
return this.value;
}
clear(){
this.value=undefined;
}
reset(){
this.value=this.defaultValue;
}
addChangeListener(listener){
this.changeListeners.push(listener);
}
removeChangeListener(listener){
this.changeListeners.remove(listener);
}
clearChangeListeners(){
this.changeListeners = [];
}
}
Example usage:
import Attribute from './attribute.js';
var name= new Attribute();
name('foo'); //set value of name to 'foo'
name.addChangeListener((oldValue, newValue)=>{
alert('value changed from ' +oldValue+ ' to ' +newValue);
});
alert(name()); //show value of name: 'foo'
name('baa'); //set value of name to new value 'baa' and trigger change listener

Categories

Resources