Using getter and setter with Object.defineProperty - javascript

I want to convert a data property to accessor property using Object.defineProperty() . Consider the code for this which leads to Uncaught RangeError: Maximum call stack size exceeded error
var c = { name: 'abcde'};
Object.defineProperty(c, 'name', {
get: function() {
return this.name; //causes stack overflow
},
set: function(x) {
this.name = x; //causes stack overflow
}
});
c.name="xyz";
console.log(c.name);
I understood why the error crops in.
One of the proposed solution is to remove 'this' from getter and setter and it seems to work.
var c = { name: 'abcde'};
Object.defineProperty(c, 'name', {
get: function() {
return name; //removed this
},
set: function(x) {
name = x; //removed this
}
});
c.name="xyz";
console.log(c.name);
What is happening ? In general , I want to ask how to convert a data property to accessor property using Object.defineProperty() ?

The second code doesn't actually work because it uses the global variable called name to store the value, instead of storing it in the object c.
It would be rejected by ES5 "strict mode", if it weren't for the fact that window.name is a default property of the global object in browsers.
A more appropriate fix would be to store the value in a lexically scoped private variable:
var c = (function() {
var obj = {};
var name = "abcde";
Object.defineProperty(obj, "name", {
get: function() {
return name;
},
set: function(x) {
name = x;
}
});
return obj;
})();

Related

What happens when setting object property without setter in javascript

var vehicle = function(){
var type;
var tyre;
this.tellTyres = function(){
console.log(type + " has " + tyre + " tyres");
};
this.__defineGetter__("type", function(){
return type;
});
this.__defineSetter__("type", function(val){
type = val;
});
this.__defineGetter__("tyre", function(){
return tyre;
});
// this.__defineSetter__("tyre", function(val){
// tyre = val;
// });
};
var car = new vehicle();
car.type = "Car";
car.tyre = 4;
console.log(car.tyre);
car.tellTyres();
I was learning about the getter and setter. Then I realized Javascript is not throwing any error while setting the value of car.tyre without having its setter method.
What happens to the car.tyre property outside the constructor. Where does the value 4 store? Does it override?
JavaScript objects are more like dictionaries than like Java objects. This means that you can set and get an object's properties just by using the property accessor operators . and []:
var obj = { foo: 'bar' };
obj.baz = 17;
console.log(obj.foo, obj.baz); // logs '"bar" 17'
And that is absolutely fine.
But sometimes, you want to do something whenever someone modifies a property of your object. In these cases, you define a getter or setter function for that property (use Object.defineProperty instead of defineGetter and defineSetter):
var obj = { foo: 'bar' };
Object.defineProperty(obj, 'baz', {
get: function () {
console.log('Someone wants to read the property "baz"!');
return 34;
},
set: function (value) {
console.log('You are not allowed to modify the property "baz"!');
}
});
obj.baz = 17; // doesn't work
console.log(obj.foo, obj.baz); // logs '"bar" 34'
When you create a new vehicle(), you create a new object, on which you can set or read properties. You don't need the getters and setters.

Is it possible to add a not shared variable to a prototype?

I'm building a function that would retrofit some of my prototypes with some common functions.
I would also like to add object instance specific variables through this mechanic, sorta like:
function give_weird_container(target) {
target.<somehow instance specific, not prototype>.underlying_container = [];
target.prototype.container_func = function(x, y, z) {
return this.underlying_container[x + 2*y + 3*z];
}
}
function my_class1() {}
give_weird_container(my_class1);
And now when I create a new instance of my_class1, it should have a property "uderlying_container" that would act the same as if I called
this.underlying_container = [];
in the constructor.
Is that possible while remaining in the confines of the give_weird_container function?
Is it possible to add a not shared variable to a prototype?
No. All properties on the prototype are shared. Instance specific properties can only be set after an instance was created.
You could however add a getter to the prototype that will create an instance specific property if it doesn't exist.
For example:
Object.defineProperty(target.prototype, 'underlying_container', {
get: function() {
if (!this._underlying_container) {
this._underlying_container = [];
}
return this._underlying_container;
},
});
The getter is shared, but the value returned is per instance.
If you don't like the fact that the getter is executed every time this.underlying_container is accessed, you could replace it with an instance property when the prototype property is called the first time:
Object.defineProperty(target.prototype, 'underlying_container', {
get: function() {
Object.defineProperty(this, 'underlying_container', {value: []});
return this. underlying_container;
},
});
Object.defineProperty(this, 'underlying_container', {value: []}); will create a new property with the same name on the instance thus shadowing the getter defined on the prototype.
To pick up #4castle's suggestion, if it is possible to mutate instances directly, then you could do something like this instead, which is a bit less "magic":
var give_weird_container = (function() {
function container_func(x, y, z) {
return this.underlying_container[x + 2*y + 3*z];
};
return function(target) {
target.underlying_container = [];
target.container_func = container_func;
};
}());
function my_class1() {}
var instance = new my_class1();
give_weird_container(instance);
You could give my_class1 a wrapper function that calls the constructor and then sets the field:
function give_weird_container(target) {
target.prototype.container_func = function(x, y, z) {
return this.underlying_container[x + 2*y + 3*z];
}
return function() {
var obj = new target();
obj.underlying_container = [];
return obj;
}
}
function my_class1() {}
my_class1 = give_weird_container(my_class1);

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 Error: cyclic __proto__ value

I want to change default window.location setter and getter functions.
The following code are successfully works. But This codes are only work limited area.
var _window = window;
(function () {
window = {};
var window = {};
Object.defineProperty(window, 'location', {
get: function () { alert('called getter '); return _window.location; },
set: function () { alert('not in my house'); }
});
window.__proto__ = _window;
alert('window.location : '+ window.location);
}());
When I remove the block,
var _window = window;
window = {};
var window = {};
Object.defineProperty(window, 'location', {
get: function () { alert('called getter '); return _window.location; },
set: function () { alert('not in my house'); }
});
window.__proto__ = _window;
alert('window.location : '+ window.location);
I met the following error.
ERROR Error: cyclic __proto__ value
I know that can't redefine non-configurable property "location" in normal environment. I have changed binding IDL properties to test these code in webkit library. How can I solve this problem?
I have another question. When I run the following codes, I met the same error.(jsc - Javascript Core, Spidermonkey, nodejs)
var o1 = { p1: 1 };
var o2 = { p2: 2 };
o2.__proto__ = o1;
var o3 = { p3: 3 };
o3.__proto__ = o2;
o1.__proto__ = o3;
JavascriptCore shell(jsc)
Exception: Error: cyclic __proto__ value
node.js (v0.10.25)
Error: Cyclic __proto__ value
Spidermonkey JavaScript-C24.2.0
1.js:7:0 TypeError: cyclic __proto__ value
Is it related? Thank you ^^
You really create a prototype chain loop in both cases.
In the closure the window is the local variable (because of hoisting), therefore you do not create a prototype loop on global window object.
var o1 = { p1: 1 };
var o2 = { p2: 2 };
var o3 = { p3: 3 };
o2.__proto__ = o1;
o1.__proto__ = o3;
o3.__proto__ = o2;
VM432:7 Uncaught TypeError: Cyclic __proto__ value
at Object.set __proto__ [as __proto__] (<anonymous>)
at <anonymous>:7:14
Reason: JavaScript will throw an error if we try to assign __proto__ in a circle.The references can’t go in circles.

Javascript overriding the property setting functionality

JavaScript is dynamic. Cool !
I have the following constructor function :
function Preferences () {
this._preferences = {}
}
var obj = new Preferences()
I want to achieve something like this:
>>> obj.something = 'value'
>>> this._preferences['something']
'value'
That is setting the property of the obj does not actually set it's own property but that of obj._preferences. That is I want to override the default behavior.
Is it possible ?
EDIT : I want to achieve this for all property names i.e the name of the property to be set is not already known.
Object.defineProperty(Preferences.prototype, 'something', {
get: function(){
return this._preferences.something;
},
set: function(value){
this._preferences.something = value;
}
});
should do it. It defines a property, 'something', using an accessor property instead of a data property, and will call the 'get' and 'set' functions to decide what do so when .something is accessed.
SOLUTION 1
Using Proxy object you can do something like this and handle runtime defined properties
function Preferences() {
this._preferences = {};
var prefProxy = new Proxy(this, {
get: function(target, property) {
return property in target?
target[property]:
target._preferences[property];
}
set: function(target, property, value, receiver) {
if(property in target){
target[property] = value;
} else {
target._preferences[property] = value;
}
}
});
return prefProxy;
};
SOLUTION 2
I can be wrong but i think what you are asking is solved returning _preferences
function Preferences () {
this._preferences = {};
return _preferences;
}
var obj = new Preferences()
SOLUTION 3
Using getter and setter you can redirect the property to _preferences
function Preferences () {
this._preferences = {}
Object.defineProperty(Preferences.prototype, 'something', {
get: function() {
return this._preferences['something'];
},
set: function(value) {
this._preferences['something'] = value;
}
});
}
var obj = new Preferences()

Categories

Resources