JS defineProperty setter doesn't triggered - javascript

Q1: Can someone explain how to trigger setter in defineProperty, using it via function by this way?
Q2: How to get last key in setter?
fiddle is here
function test(root) {
Object.defineProperty(this, 'subtree', {
get: function() {
console.log("get");
return root.subtree;
},
set: function(value) { //doesn't triggered
console.log("set");
root.subtree = value;
}
});
}
var demo = new test({
subtree: {
state: null,
test: 1
}
});
console.log("START", demo.subtree);
demo.subtree.state = 13; // doesn't triggered setter, but change object
console.log("END", demo.subtree);

To make it simpler, this code
let variable = null;
let obj = {
set variable(value) {
variable = value;
}
get variable() {
return variable;
}
};
obj.variable = {a: 5};
console.log(obj.variable);
does exactly the same thing as this one
let variable = null;
let obj = {
setVariable(value) {
variable = value;
}
getVariable() {
return variable;
}
};
obj.setVariable({a: 5}); // equivalent to obj.variable = {a: 5}
console.log(obj.getVariable()); // equivalent to obj.variable
but the latter clearly shows what's going on.
We want to access a and set it to some value
console.log(obj.getVariable().a); // get a
obj.getVariable().a = 6; // set a!
Notice that we don't call setVariable to set a's value!!! This is exactly what happens in your code. You get subtree and set state to 13. To call setter, you do the following
obj.setVariable({a: 6});
obj.variable = {a: 6}; // getter/setter example
demo.subtree = {state: 13}; // your code
This and this (linked by you) present how scopes and capturing work, so you should get your hands on some book that covers all those things (or browse SO, there are (probably) plenty of questions about that).

Related

Is there any point to use getters/setters for an object?

It seems to me that using getters and setters for an object inside a class has no point to it. As I understand it, get/set is useful because it prevents someone outside the class changing something that shouldn't be changed or changing it to something it shouldn't be. However it seems pointless for objects. For example, I have a person with an address, I want to prevent editing the address, you can only view it:
class Person{
constructor(name, address){
this._name = name;
this._address = address;
}
get address(){
return this._address;
}
}
let bob = new Person("bob", {
street: "123 Main Street",
city: "Los Angelos",
state: "California"
});
But then you can still edit it like this:
let address = bob.address;
address.state = "New York";
To prevent this, I would think that you have to return a copy of the object instead of the reference. However, as far as i know, there is no standard way to deep clone an object. So you either have to shallow clone it, which seems not ideal if you have lots of nested references, or just return the reference to the object, which can be edited.
Am I missing something here?
Consider this class.
class Test {
constructor(val) {
this._foo = val;
}
set foo(val) {
throw new Error("It's not possible to change the foo property!")
}
set boo(val) {
console.log("setting _boo")
this._boo = val;
}
get foo() {
console.log("getting _foo");
return this._foo;
}
}
try {
let test = new Test('foooooo');
test.boo = "booooo";
console.log(`foo: ${test.foo}`);
test.foo = "bar";
} catch (e) {
console.log(e);
}
With "Setter" it's possible to control initializing properties.
You can see that it's possible to change the value of "boo" property but any attempt to change the value of the "foo" will throw an exception.
With "Getter" it's possible to control retrieving the value of properties.
You can see that it's possible to retrieve the value of "foo" property but not "boo" and its value is private.
PS:
Here are some examples to better understand JS behavior with objects and arrays:
//This works fine:
//Object
const obj = {};
obj.foo = 'bar';
console.log(obj); // {foo : 'bar'}
obj.foo = 'bar2';
console.log(obj); // {foo : 'bar2'}
//---------------------------------------
//Array:
const arr = [];
arr.push('foo');
console.log(arr); // ['foo']
arr.unshift("foo2");
console.log(arr); // ['foo2', 'foo']
arr.pop();
console.log(arr); // ['foo2']
//===========================================
//but these won't work:
const obj = {};
obj = {foo: 'bar'}; // error - re-assigning
const arr = ['foo'];
const arr = ['bar']; // error - re-declaring
const foo = 'bar';
foo = 'bar2'; // error - can not re-assign
var foo = 'bar3'; // error - already declared
function foo() {}; // error - already declared
New Example:
class A {
constructor(val) {
this._foo = val;
}
set foo(val) {
throw new Error("It's not possible to change the foo property!")
}
get foo() {
return this._foo;
}
}
class B {
constructor(val) {
this._obj = new A(val);
}
get Obj() {
return this._obj;
}
}
let b = new B('Test');
b.Obj.foo = 'new value';
console.log(b.Obj.foo);
In this manner, it's not possible to change the values ​​of the internal object.

Javascript create reference to an object property?

I understand that in javascript, primitives are passed by value and objects are passed by reference.
I'm interested in creating a workaround of some kind that would let me get a reference to an object property containing a primitive. For example, what I wish would work is:
var someObject = {a: 1, b: 2};
var myRef = someObject.b;
myRef ++;
someObject.b #=> 3
Of course, this doesn't work. I'm aware that you could create a getter and setter function instead, or use one object to reference another object, but what I'd really like is some kind of workaround that allowed me to define a variable as a reference to the property of another object, and so far it seems this just can't be done.
So, my question is simply: is this even possible, and if so, how?
Primitive types are immutable, so no, it's not possible. You can wrap your primitive type with an object, like this:
function MyNumber(n) { this.n = n; }
MyNumber.prototype.valueOf = function() { return this.n; }
var someObject = { a: 1, b: new MyNumber(2) };
var myRef = someObject.b;
MyNumber.call(myRef, myRef + 1);
console.log(+someObject.b); // convert to number with +
OR
var someObject = {
a: { value: 1 },
b: { value: 2 },
};
var myRef = someObject.b;
my_inc(myRef); // function my_inc (obj) { obj.value++; }
// someObject.b.value == 3
The React framework uses a very simple pattern to encapsulate values.
function Link(value, requestChange)
{
this.value = value;
this.requestChange = requestChange;
}
You can pass around the object, the current value can be accessed by inspecting the value property of the object, if you want to change it you can call requestChange with a new value, you can change the value. The advantage would be to have the actual "storage location" and the logic for changing the value decoupled from the value read and write access. Note that the values can also be complex objects.
You could also achieve something similar with closures:
var someObject = {
a: 1,
b: 2
};
function property(object, prop) {
return {
get value () {
return object[prop]
},
set value (val) {
object[prop] = val;
}
};
}
var ref = property(someObject, "b");
ref.value; // 2
++ref.value; // 3
someObject.b; // 3
This works because the getter and setter functions have access to whatever bindings were in scope at the time of their creation (object and prop). You can now pass ref around, store it in a data structure, etc.
No, there isn't a nice way to do it.
You can use a work-around if you want to. Something like wrapping all your primary data types with single element arrays:
var someObject = {a: [1], b: [2]};
var myRef = someObject.b;
myRef[0]++;
someObject.b[0]; // 3
That's less than ideal though, as you have to use [0] to access the property all the time. There are some cases where it can be useful though, and the default toString of a single element array is just the toString of its element, so you can use the property directly in a string context:
console.log('My value: ' + someObject.b); // 'My value: 3'
if you want to "link" or "synchronize" two properties , each of a different object, you could do it like this:
var someObject = {
a: 1,
b: 2
};
var linkedObject = {
a:1,
b:2
}
function property(object, prop) {
return {
get value () {
return object[prop]
},
set value (val) {
object[prop] = val;
}
};
}
var s_prop = 'b'
var o_ref = property(someObject, s_prop);
var tmp = linkedObject[s_prop];
Object.defineProperty(
linkedObject,
s_prop,
{
set: function(value) {
o_ref.value = value;
},
get: function() {
return o_ref.value
}
}
);
linkedObject[s_prop] = tmp
someObject.b = 333 /// linkedObject.b is also 333 now
console.log(someObject.b) // 333
console.log(linkedObject.b)// 333
linkedObject.b = {"test": 2}
console.log(someObject.b) // {test:2}
console.log(linkedObject.b)// {test:2}
someObject.b.test = 3
console.log(someObject.b) // {test:3}
console.log(linkedObject.b)//{test:3}
I don't know how satisfying this is, but you could do it if you were ok with wrapping the desired object in an object like so:
var a = {a:{a:1},b:2};
var b = a.a;
b.a++;
a.a.a //=> 2
It isn't exactly what you asked for, but it would work.

How to conditionally add properties to a javascript object literal

I am trying to do the following to satisfy the requirements of a code builder (Sencha Cmd to be specific).
This is the essence I what I need to do. The critical factor is that the function body MUST end with a return of an object literal. I cant return a variable due to restrictions in the builder. So, how to add a property 'b' at the point of the pseudo code below if the parameter 'includeB' is true, but NOT add a property AT ALL if it is false. ie b==undefined or b==null is not allowed.
Perhaps it is not possible.
function create(includeB) {
// Can have code here but the final thing MUST be a return of the literal.
// ...
return {
a : 1
// pseudo code:
// if (includeB==true) then create a property called b
// and assign a value of 2 to it.
// Must be done right here within this object literal
}
}
var obj = create(false);
// obj must have property 'a' ONLY
var obj = create(true);
// obj must have properties 'a' and 'b'
Thanks for reading and considering,
Murray
If you can use ES6, use the spread properties.
function create(includeB) {
return {
a : 1,
...(includeB ? { b: 2 } : {}),
};
}
You've pretty much shown a use case for a constructor function instead of using an object literal:
function CustomObject(includeB) {
this.a = 1;
if (includeB) {
this.b = 2;
}
}
//has `a` only
var obj1 = new CustomObject(false);
//has `a` and `b`
var obj2 = new CustomObject(true);
After re-reading your question it appears that you've got limited access in modifying the function. If I'm understanding your question correctly you can only change a limited portion of the script:
function create(includeB) {
// modifications may be done here
// the rest may not change
return {
a : 1
}
}
var obj = create(false);
// obj must have property 'a' ONLY
var obj = create(true);
// obj must have properties 'a' and 'b'
If that's the case, then you could simply skip the later part of the function:
function create(includeB) {
if (includeB) {
return {
a: 1,
b: 2
};
}
return {
a: 1
};
}
You cannot put boolean logic inside a javascript literal definition. So, if your builder requires the the returned object can ONLY be defined as a javascript literal, then you cannot define properties conditionally that way.
If you can create an object inside your function, modify that object using logic and then return that object, then that's pretty easy.
function create(includeB) {
var x = {
a: 1
};
if (includeB) {
x.b = 2;
}
return x;
}
Your other option would be to wrap the create function and do it outside the create function.
function myCreate(includeB) {
var x = create(includeB)
if (includeB) {
x.b = 2;
}
return x;
}
Or, you could even wrap the create function transparently so callers still use create(), but it's behavior has been altered.
var oldCreate = create;
create = function(includeB) {
var x = oldCreate(includeB);
if (includeB) {
x.b = 2;
}
return x;
}
I recently had to do this, and found you could use a self-calling function within an object's definition (if using ES6). This is similar to the accepted answer, but might be useful for others who need to do this without first defining a constructor function.
For example:
let obj = (() => {
let props = { a: 1 };
if ( 1 ) props.b = 2;
return props;
})();
makes the object: { a: 1, b: 2 }
It's handy for more complicated objects, keeping the construction continuous:
let obj = {
a: 1,
b: (() => {
let props = { b1: 1 };
if ( 1 ) props.b2 = 2;
return props;
})(),
c: 3
}
makes the object:
{
a: 1,
b: {
b1: 1,
b2: 2
},
c: 3
}
You could define it later:
var hasA = create(); // has hasA.a
var hasBoth = create();
hasBoth.b = 2; //now has both
Alternatively, using your argument in create:
function create (includeB) {
var obj = {
a : 1
};
if (includeB) {
obj.b = 2;
}
return obj;
}
Below should work. I hope this help.
function create(includeB){
var object = {
a: 1
};
if (includeB)
object.b = 2;
return object;
}
How about this:
function create(includeB) {
return includeB && { a:1, b:2 } || { a:1 };
}
When includeB is true, the create function will return {a:1, b:2}. If includeB is false, it will return whatever is after the or - in this case, the {a:1} object.
create(true) returns { a:1, b:2 }.
create(false) returns { a:1 }
If you would like to use a declaration to satisfy the same requirement once without too much bloat, you can also simply do the following:
var created = function(includeB) {
var returnObj = { a : 1 };
if(includeB) { returnObj.b = 2; }
return returnObj;
}}(); //automatically runs and assigns returnObj to created

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.

How to check if an object has been modified

I have an object, for example:
var o = {
a: 1
};
The user does:
o.a = 2;
How can I know if the o object has been modified? I cannot touch the o object so I cannot use Object.defineProperties().
Since you are in the node.js environment and thus don't have to care about crappy old JavaScript engines (i.e. old browsers) you can use Object.defineProperty() to define properties with accessor functions. This allows you to execute a custom function whenever a certain property is read/written - so you can simply log the write and e.g. store it in a separate property.
var o = {};
Object.defineProperty(o, 'a', {
get: function() {
return this.__a;
},
set: function(value) {
this.__a = value;
this.__a_changed = true;
}
});
o.__a = 1;
Whenever a value is assigned to o.a the __a_changed property will be set. Of course it would be even cleaner to execute whatever you want to do on change right in the set function - but it obviously depends on your code if you can do so in a useful way.
The easiest thing would obviously be to just check if the value is different than what you initialized it as. Another option would be to use Object.defineProperty.
var o = {};
var aValue = 2;
Object.defineProperty(o, "a", {
get: function() { return aValue; },
set: function(newValue) {
aValue = newValue;
// trigger event or set flag that it was changed
}
});
You could always wrap o with your own setter getter if the defineProperty doesn't do the job for you.
var myO = {
o: o, // original o
changed: false,
set: function (prop, val) {
this.o[prop] = val;
this[prop + "IsChanged"] = true;
},
get: function (prop) {
return this.o[prop];
}
};
myO.set("a", 2);
console.log(myO.aIsChanged); // true
A better solution would be to execute an event using something like Backbone.Events to exeute someting myO.on("change", function () { ... })
var myO = {
o: o, // original o
changed: false,
set: function (prop, val) {
this.o[prop] = val;
this.trigger("change", prop, val);
},
get: function (prop) {
return this.o[prop];
}
};
_.extend(myO, Backbone.events);
myO.on("change", function (prop, val) {
// Do stuff with the change
});
myO.set("a", 1);
I'm using the Underscore library's extend method here, fyi.
var o_backup = o.a;
if(o.a != o_backup){
// function to execute
}
Basically, you are just creating a variable which will save the a from the object o and then you're checking if it has been modified or not.
Also, you can do a setInterval() function if you want to check if it has been modified more than once.

Categories

Resources