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.
Related
Given an object obj, I would like to define a read-only property 'prop' and set its value to val. Is this the proper way to do that?
Object.defineProperty( obj, 'prop', {
get: function () {
return val;
}
});
The result should be (for val = 'test'):
obj.prop; // 'test'
obj.prop = 'changed';
obj.prop; // still 'test' since it's read-only
This method works btw: http://jsfiddle.net/GHMjN/
I'm just unsure if this is the easiest / smoothest / most proper way to do it...
You could instead use the writable property of the property descriptor, which prevents the need for a get accessor:
var obj = {};
Object.defineProperty(obj, "prop", {
value: "test",
writable: false
});
As mentioned in the comments, the writable option defaults to false so you can omit it in this case:
Object.defineProperty(obj, "prop", {
value: "test"
});
This is ECMAScript 5 so won't work in older browsers.
In new browsers or node.js it is possible to use Proxy to create read-only object.
var obj = {
prop: 'test'
}
obj = new Proxy(obj ,{
setProperty: function(target, key, value){
if(target.hasOwnProperty(key))
return target[key];
return target[key] = value;
},
get: function(target, key){
return target[key];
},
set: function(target, key, value){
return this.setProperty(target, key, value);
},
defineProperty: function (target, key, desc) {
return this.setProperty(target, key, desc.value);
},
deleteProperty: function(target, key) {
return false;
}
});
You can still assign new properties to that object, and they would be read-only as well.
Example
obj.prop
// > 'test'
obj.prop = 'changed';
obj.prop
// > 'test'
// New value
obj.myValue = 'foo';
obj.myValue = 'bar';
obj.myValue
// > 'foo'
In my case I needed an object where we can set its properties only once.
So I made it throw an error when somebody tries to change already set value.
class SetOnlyOnce {
#innerObj = {}; // private field, not accessible from outside
getCurrentPropertyName(){
const stack = new Error().stack; // probably not really performant method
const name = stack.match(/\[as (\w+)\]/)[1];
return name;
}
getValue(){
const key = this.getCurrentPropertyName();
if(this.#innerObj[key] === undefined){
throw new Error('No global param value set for property: ' + key);
}
return this.#innerObj[key];
}
setValue(value){
const key = this.getCurrentPropertyName();
if(this.#innerObj[key] !== undefined){
throw new Error('Changing global parameters is prohibited, as it easily leads to errors: ' + key)
}
this.#innerObj[key] = value;
}
}
class GlobalParams extends SetOnlyOnce {
get couchbaseBucket() { return this.getValue()}
set couchbaseBucket(value){ this.setValue(value)}
get elasticIndex() { return this.getValue()}
set elasticIndex(value){ this.setValue(value)}
}
const _globalParams = new GlobalParams();
_globalParams.couchbaseBucket = 'some-bucket';
_globalParams.elasticIndex = 'some-index';
console.log(_globalParams.couchbaseBucket)
console.log(_globalParams.elasticIndex)
_globalParams.elasticIndex = 'another-index'; // ERROR is thrown here
console.log(_globalParams.elasticIndex)
Because of the old browsers (backwards compatibility) I had to come up with accessor functions for properties. I made it part of bob.js:
var obj = { };
//declare read-only property.
bob.prop.namedProp(obj, 'name', 'Bob', true);
//declare read-write property.
bob.prop.namedProp(obj, 'age', 1);
//get values of properties.
console.log(bob.string.formatString('{0} is {1} years old.', obj.get_name(), obj.get_age()));
//set value of read-write property.
obj.set_age(2);
console.log(bob.string.formatString('Now {0} is {1} years old.', obj.get_name(), obj.get_age()));
//cannot set read-only property of obj. Next line would throw an error.
// obj.set_name('Rob');
//Output:
//========
// Bob is 1 years old.
// Now Bob is 2 years old.
I hope it helps.
I tried and it Works ...
element.readOnly = "readOnly" (then .readonly-> true)
element.readOnly = "" (then .readonly-> false)
According to MDN,
handler.set() can trap Inherited property assignment:
Object.create(proxy)[foo] = bar;
In which case, how does one both monitor and allow local assignments on inherited objects?
var base = {
foo: function(){
return "foo";
}
}
var proxy = new Proxy(base, {
set: function(target, property, value, receiver){
console.log("called: " + property + " = " + value, "on", receiver);
//receiver[property] = value; //Infinite loop!?!?!?!?!
//target[property] = value // This is incorrect -> it will set the property on base.
/*
Fill in code here.
*/
return true;
}
})
var inherited = {}
Object.setPrototypeOf(inherited, Object.create(proxy));
inherited.bar = function(){
return "bar";
}
//Test cases
console.log(base.foo); //function foo
console.log(base.bar); //undefined
console.log(inherited.hasOwnProperty("bar")) //true
After some additional thought, i noticed that it intercepts 3 ops:
Property assignment: proxy[foo] = bar and proxy.foo = bar
Inherited property assignment: Object.create(proxy)[foo] = bar
Reflect.set()
but not Object.defineProperty() which appears to be even lower level than the = operator.
Thus the following works:
var base = {
foo: function(){
return "foo";
}
};
var proxy = new Proxy(base, {
set: function(target, property, value, receiver){
var p = Object.getPrototypeOf(receiver);
Object.defineProperty(receiver, property, { value: value }); // ***
return true;
}
});
var inherited = {};
Object.setPrototypeOf(inherited, Object.create(proxy));
inherited.bar = function(){
return "bar";
};
// Test cases
console.log(base.foo); // function foo
console.log(base.bar); // undefined
console.log(inherited.bar); // function bar
console.log(inherited.hasOwnProperty("bar")) // true
I see two options (maybe):
Store the property in a Map, keeping the Maps for various receivers in a WeakMap keyed by the receiver. Satisfy get by checking the Map and returning the mapping there instead of from the object. (Also has.) Slight problem is that you also need to proxy the receiver (not just base) in order to handle ownKeys. So this could be unworkable.
Temporarily get the proxy out of the inheritance chain while setting.
Here's that second one:
var base = {
foo: function(){
return "foo";
}
};
var proxy = new Proxy(base, {
set: function(target, property, value, receiver){
const p = Object.getPrototypeOf(receiver); // ***
Object.setPrototypeOf(receiver, null); // ***
receiver[property] = value; // ***
Object.setPrototypeOf(receiver, p); // ***
return true;
}
});
var inherited = {};
Object.setPrototypeOf(inherited, Object.create(proxy));
inherited.bar = function(){
return "bar";
};
// Test cases
console.log("base.foo:", base.foo); // function foo
console.log("base.bar:", base.bar); // undefined
console.log("inherited.bar:", inherited.bar); // function bar
console.log("inherited has own bar?", inherited.hasOwnProperty("bar")); // true
Basically I have something like this:
MyClass
var MyClass = function() {
this.num = 123;
this.obj = new MyInnerClass();
};
MyClass.prototype.stringify = function() {
return JSON.stringify(this);
};
MyInnerClass
var MyInnerClass = function() {
this.foo = 456;
this.bar = 'bonjour!';
};
MyInnerClass.prototype.stringify = function() {
return JSON.stringify(this, function(k, v) {
// ignores 'foo' attribute
return k !== 'foo' ? v : undefined;
});
};
Each class has its own stringify implementation, so when I do:
var mc = new MyClass();
mc.stringify();
I would like something like calling MyClass.stringify should stringify my mc object, but respecting inner objects stringify implementations. Once we don't have control over the JSON.stringify method logic, is there a good way to do that?
Thank you!
If you look on MDN at JSON.stringify, you'll see a section that talks about a toJSON property
If an object being stringified has a property named toJSON whose value is a function, then the toJSON() method customizes JSON stringification behavior: instead of the object being serialized, the value returned by the toJSON() method when called will be serialized.
Basically, define a toJSON method for your Object which creates another Object, but one that can be serialised as you desire. Then JSON.stringify will serialise the return of your toJSON function instead, i.e.
var MyClass = function() {
this.num = 123;
this.obj = new MyInnerClass();
};
var MyInnerClass = function() {
this.foo = 456;
this.bar = 'bonjour!';
};
MyInnerClass.prototype.toJSON = function () {
// shallow clone `this`, except .foo
var o = Object.create(null), k, blacklist = ['foo'];
for (k in this) // loop over enumerable properties
if (Object.prototype.hasOwnProperty.call(this, k)) // ignore inherited properties
if (blacklist.indexOf(k) === -1) // ignore blacklisted properties
o[k] = this[k]; // add to our clone
return o;
};
JSON.stringify(new MyClass()); // '{"num":123,"obj":{"bar":"bonjour!"}}'
This will also replace your need for the current stringify method.
Sadly you can't call JSON.stringify(this) inside .toJSON because it becomes circular and you get RangeError: Maximum call stack size exceeded, but you'd not get the desired result this way anyway as it would be serialised a second time, giving you a String in your JSON.
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
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');