I thought I understood the purpose of get and set functions in accessors. But in a tutorial (W3Schools) they give this example. Notice that the first three are get and the last two are set. Why aren't they all set if we are trying to mutate counter?
// Define an object
var obj = {counter:0};
// Define Setters and Getters
Object.defineProperty(obj, "reset", {
get : function () {this.counter = 0;}
});
Object.defineProperty(obj, "increment", {
get : function () {this.counter++;}
});
Object.defineProperty(obj, "decrement", {
get : function () {this.counter--;}
});
Object.defineProperty(obj, "add", {
set : function (value) {this.counter += value;}
});
Object.defineProperty(obj, "subtract", {
set : function (value) {this.counter -= value;}
});
It's not really about mutating / not mutating here but rather about needing to pass an argument (set - on the right hand side of the = operator) or not (get). If you look at the example given by W3Schools:
// Play with the counter:
obj.reset;
obj.add = 5;
obj.subtract = 1;
obj.increment;
obj.decrement;
you'll notice that add and subtract (the "setters") have arguments (5 and 1) whereas reset / increment / decrement don't have any arguments ("getters").
set requires a value on the right side of the = operator.
obj.add = 5
get requires no = at all, and therefore no other value.
obj.increment
That said, this is a very contrived example. If your getters and setters name's are verbs, you probably just want them to be functions.
The reason set is used in the add and subtract methods is because they take an argument to the function, whereas increment and decrement do not. By definition, getters do not take arguments, while setters do. Technically they could have used set with increment and decrement, but it really doesn't make a whole lot of difference, and to be quite honest, I don't see a whole lot of people using getters/setters to begin with.
Here's a stack overflow post explaining more about the difference between set/get with arguments
Syntax
Object.defineProperty(obj, prop, descriptor)
obj:The object on which to define the property.
prop:The name or Symbol of the property to be defined or modified.
descriptor:The descriptor for the property being defined or modified.
set : In set we pass an argument.
get : In get we use an argument/Pre-defined arguement.
// Define an object
var obj = {counter:0};
// Define Setters and Getters
Object.defineProperty(obj, "reset", {
get : function () {this.counter = 0;}
});
Object.defineProperty(obj, "increment", {
get : function () {this.counter++;}
});
Object.defineProperty(obj, "decrement", {
get : function () {this.counter--;}
});
Object.defineProperty(obj, "add", {
set : function (value) {this.counter += value;}
});
Object.defineProperty(obj, "subtract", {
set : function (value) {this.counter -= value;}
});
I hope you understand why we are used like this here.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
Related
I can have a getter in a JavaScript object like this:
var member = {
firstName:"XYZ",
lastName:"zzz",
get fullName(){ return (this.firstName + ' ' + this.lastName); }
}
I can even add more properties on the fly, like this:
member.isGuest = true;
But, is there any way we can add getters to an existing object? Something like this:
member.isGuest = get isGuest(){ return this.firstName == 'Guest'; }
try defineProperty
Object.defineProperty(member, 'isGuest', {
get: function() { return this.firstName=='Guest' }
});
You can use the new Object.defineProperty this way:
Object.defineProperty(
member,
'prop',
{
get: function() {
return this.lastName.toUpperCase()
}
}
);
In the past you had to use __defineGetter__ but now it has been deprecated.
This is possible not only with the above solutions, but also using the ... operator.
// Initialize x
var x = {
x: 5
}
// Log x.x and x.y
console.log(x.x, x.y /* undefined */)
x = {
...x, // {...x} is the same as {x: x.x}
// The getter
get y() {
return this.x
}
}
// Log x.x and x.y
console.log(x.x, x.y /* Not undefined */)
// Set x.x to 1234
x.x = 1234
// Log x.x and x.y
console.log(x.x, x.y)
... is the spread operator, which "spreads" the contents of the object it is used on, hence the name. For example:
doSomething(...[1, 2, 3]) is the same as doSomething(1, 2, 3)
{ ...{x: 1, y: 2, z: 3} } is the same as { x: 1, y: 2, z: 3 }
Moreover, you can add a custom getter and preserve the old value if it is not supposed to be calculated from other values, like in your example. To do so, create additional context layer using an anonimous function like this:
(function() {
const oldValue = yourObject[targetProperty];
let currentValue = oldValue;
const getter = function() {
return currentValue; // or do something before returning, like applying parseInt or whatever
};
const setter = function(newValue) {
currentValue = newValue; // or add some customization as well
};
Object.defineProperty(yourObject, targetProperty, {
get: getter,
set: setter,
enumerable: true, // depending on your needs
configurable: true // depending on your needs
});
})();
For the good old purposes of completeness, Reflect.defineProperty is also an option :). Note that Object.defineProperty and Reflect.defineProperty do not behave the same.
MDN compares the two functions (as well as all similar Object and Reflect methods):
Method Name
Object
Reflect
defineProperty()
Object.defineProperty() returns the object that was passed to the function. Returns a TypeError if the property was not successfully defined on the object.
Reflect.defineProperty() returns true if the property was defined on the object and false if it was not.
I prefer Reflect because I think boolean checks make more sense, but in general remember to error handle if you think that there may be conflicts (these should be infrequent when messing with object properties, unless one of your libraries is making similar, possibly conflicting changes).
You can only use get and set in a Class, It's mean that getter and setter method. but you can add a function to class:
member.isGuest = function isGuest(){ return this.firstName=='Guest';}
member.isGuest()
the get means that property can be read!
the set means that property can be write!
You can look it in book 《JavaScript:The.Definitive.Guide》6edition the 6 chapter!
When using:
Object.defineProperty(obj,prop,desc){
get: function(){...
set: function(){...
}
Does the getter/setter apply to obj[prop] or does it act on obj no matter what property is specified?
I am asking because I'm trying to setup some data binding based on a nested object like:
obj[propA] = {propB:'seomthing',propC:'somethingElse'}
and when I do something like this:
var obj = {value:{propA:'testA',propB:'testB'}};
Object.defineProperty(obj.value,'propA',{
get: function(){return this.value;},
set: function(newValue){this.value=newValue;console.log('propA: ',newValue);}
});
console.log(obj.value.propA);
obj.value.propA = 'testA';
Object.defineProperty(obj.value,'propB',{
get: function(){return this.value;},
set: function(newValue){this.value=newValue;console.log('propB: ',newValue);}
});
console.log(obj.value.propB);
obj.value.propB = 'testB';
console.log('propA: ',obj.value.propA,' --propB: ',obj.value.propB);
the getter assigns the value to ALL the properties set by defineProperty within the object.
If this is the correct functionality, is there a way to have the getter/setter work only on the property defined such that in the fiddle above, propA would yield testA and propB would yield testB?
The getter and setter only apply to the named property, but this inside each one refers to the object whose property it is (you don’t have to have a backing variable for every property).
In your example, you’re always reading and modifying obj.value.value. You can create a different variable for each one by wrapping each in an IIFE, for example:
(function () {
var value;
Object.defineProperty(obj.value, 'propA', {
get: function () { return value; },
set: function (newValue) { value = newValue; },
});
})();
Updated fiddle
Ok, I try to create new object this way:
var src = {a:'a', b:'b', c:'c'};
var out = {};
for(var prop in src){
Object.defineProperty(out, prop,{
get: function(){
return src[prop];
},
set: function(val){
src[prop]=val;
}
})
}
And get a bad result:
out = {a:'c', b:'c', c:'c'}
I know other ways to create this object, so as:
for (var prop in src) {
(function(prop) {
Object.defineProperty(out, prop, {
get: function() {
return src[prop];
},
set: function(val) {
src[prop] = val;
}
})
})(prop)
}
or:
Object.keys(src).map(function(prop){
Object.defineProperty(out, prop,{
get: function(){
return src[prop];
},
set: function(val){
src[prop]=val;
}
})
})
But I can't understand why, in the first method, a string parameter "prop" will be sent to the function 'defineProperty' by link. Help me to understand this please.
Sorry for bad english.
When you create a function inside a loop you create a closure around the variables used in that loop. In this case there is a closure around prop. Each function (the getters) has a reference to prop so when they are called later on (when the getter is used) they use the value in prop which happens to be the last value that was assigned in the loop.
In other words, since the getter is called later, the value in prop is whatever value it was last set to. defineProperty, on the other hand, gets the correct value since there is no closure. It is called with the value at the time of the call rather than after the loop is complete.
Does anyone have any example implementation of making individual object props readOnly/non-configurable? I mean primitive data types. Have tried using ES5 Object API, but hitting a brick wall.
I can't show code, because it's still at that "messy" phase, but basically I'm iterating through an outside object which, itself, holds numeruos objects. Those objects each hold various primitive data types. I have made the outer objects readOnly, non-config, etc, but can't figure out how to do likewise for individual props, the innermost props.
So, if outer.inner.prop === "Hello", I want to make that value readOnly.
Thanks!
UPDATE
I just figured this out, it was all in the for loop I was using to iterate over props. Now I've actually get data descriptors for the props, even the primitive ones. :) Thanks all!
You have to iterate through the inner object, since there is no way to deep-freeze an object using standard ES5 methods.
function deepFreeze(obj) {
Object.keys(obj).forEach(function (key) {
if (typeof obj[key] == 'object')
deepFreeze(obj[key]);
});
Object.freeze(obj);
}
Edit:
Also works for defineProperty if you don't want to freeze:
function deepWriteProtect(obj) {
Object.keys(obj).forEach(function (key) {
if (typeof obj[key] == 'object')
deepWriteProtect(obj[key]);
Object.defineProperty(obj, key, { writable: false });
});
}
I'm not 100% sure I understand your question correctly, but from what I gather you are asking for private variables. If so, that can be easily achieved using closures.
function myClass(){
var mySecretProperty = 10;
this.getMySecretProperty = function(){
return mySecretProperty;
}
this.changeMySecretProperty = function(s){
// whatever logic you need for a setter method
mySecretProperty = s;
}
}
var myObj = new MyClass();
myObj.changeMySecretProperty(120);
myObj.getMySecretProperty(); // will return 120
myObj.mySecretProperty // will return undefined
Would the following (ES5) example help? It creates an empty constructor, with a getter for property a (and no setter, so de facto a is read only):
var Obj = function(){};
Obj.prototype = {
get a() {return 5;}
}
var x = new Obj;
alert(x.a); //=> 5
x.a = 6; //=> TypeError: setting a property that has only a getter
Not using ES5 you can do
var Obj = function(){
var a = 5;
if (!Obj.prototype.getA) {
Obj.prototype.getA = {
toString: function() {
return a;
}
};
}
}
var y = new Obj;
alert(y.getA); //=> 5
But that is not 100% failsafe: Obj.prototype.getA can be overwritten.
Here is a jsfiddle showing how you can use ES5 getter/setter definitions to make a property of an object something that can only be fetched. The code looks like this:
var object = {
get x() {
return 17;
}, set x() {
alert("You cannot set x!");
}
};
Of course the getter could obtain the value of the property ("x") from anywhere, like a closure from a constructor or something. The point is that the setter simply does not change the value, so attempts to change it:
object.x = 100;
will not have any effect.
This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
detect variable change in javascript
How can i found out that my variable has changed?
I have an event that performed every time when my variable is for example == 1, i want it to perfome only when my variable changes.
You can do that via a property setter, which is a newly-standardized part of the language (new as of the ECMAScript5 specification). That will only work for a property defined with a setter on an object, not with any old variable or property.
Here's an example of an object with a foo property with both a getter and a setter:
var obj = {};
Object.defineProperty(obj, "foo", (function(){
var value = 42;
function fooGet() {
display("Getting: " + value);
return value;
}
function fooSet(newValue) {
display("Setting: " + newValue);
value = newValue;
}
return {
get: fooGet,
set: fooSet
};
})());
Live example, but only works on browsers with support for ECMAScript5 properties, which I think is just Google Chrome at the moment
Or of course, just directly use getter and setter functions rather than properties. :-)
Simple: Don't use a primitive variable but an object which has a "setter" method to change the value. In the setter, you can call additional code.
You can't do this with primitives. The only way it would be possible is if you encapsulated the variable in an object, and added some custom events to this object.
#T.J. I think #Aaron is suggesting an object that does something like this:
var myVar = new Watched(1, function (newValue) { alert(newValue) });
myVar.add(2); // alert(3);
myVar.add(10); // alert(13)
Where Watched maintains an internal value that is updated by methods, which fire a callback to say they have updated.
EDITED:
Ok maybe not what #Aaron was thinking:)
I had something very simple like this in mind:
function Watched (initVal, cb) {
var value = initVal;
function add (val) {
value += val;
cb(value);
}
// return public methods
return { add: add };
}
You could use the setInterval method to periodically check the variable's value and do your stuff if it's what you want.
It's not a nice solution, though. The other suggestions are better.
http://www.elated.com/articles/javascript-timers-with-settimeout-and-setinterval/
EDIT:
var myVar = 0;
var intervalId = setInterval('checkVariable(myVar);', 3000);
function checkVariable(theVar) {
if (theVar == 1) {
alert('Variable is 1.');
}
}
That will execute checkVariable(myVar) every 3 seconds.