I want to execute a function when the value of props.test is changed. I have seen in another question Object.defineProperty can be used. But in my case it will props.test, how to make it work
props.test = props.test || {};
//if props.test value changes it need to execute set function
props.test = "abc";
function setTest(){
console.log("set function is executed");
props.test = "abc";
}
This could be achieved by defining getter and setters on an object such as,
var props = {
get value() {
return this._value;
},
set value(val) {
this._value = val;
alert("value is: " + val);
}
}
For the above example, when you set the value on the props object, an alert function is executed,
props.value; // returns undefined
props.value = "hello"; // alert box will appear, "value is: hello"
props.value; // returns "hello".
This can be replaced with the function you wish to execute when the value changes.
When adding properties onto an existing object you can use Object.defineProperty like so,
var me = {name:'Anthony', location:'RI'};
// let's extend onto the object a new property called, 'age'
// with custom getter and setter
Object.defineProperty(me, 'age', {
get: function() {
return this._age;
},
set: function(newAge) {
this._age = newAge;
alert("I am " + newAge + " now!");
}
});
me.age; // undefined
me.age = 28; // alert box will appear, "I am 28 now!"
me.age; // returns 28.
To take it one-step further if the pre-existing object contains an object, you can perform the following,
Note: Object.defineProperty takes as arguments an, object, key, and descriptor (ie, our custom setters and getters) respectively. That object argument can be any object type, even the nested object of the parent object.
// my pet rules my life, so let's include her in my example object,
var me = {name:'Anthony', location:'RI', pet:{name:'Binks'}};
// let's give her an identifier because that's what good owners do,
// Notice the first argument of, 'me.pet' which is the inner object.
Object.defineProperty(me.pet, 'id', {
get: function() {
return this._id;
},
set: function(newId) {
this._id = newId;
alert("id changed! " + newId);
}
});
me.pet['id']; // undefined
me.pet['id'] = 1; // alert box will appear, "id changed! 1"
me.pet['id']; // returns 1
Update: 07/12/2015
If you are trying to add dynamic values onto an Object, you can extend the above functionality into a generalized function,
var defineProp = function(obj, value) {
Object.defineProperty(obj, value, {
get: function() {
return this['_' + value];
},
set: function(newValue) {
this['_' + value] = newValue;
alert(value + " changed! " + newValue);
}
});
}
The most logical way of doing this is to create a function that is responsible for setting the property. To the best of my knowledge, there is no hook available to monitor when the property of an object has been changed. Alternatively, you object could use a timer to periodically check to see if the property has changed.
Related
I'm trying to programmatically add and delete (for caching purposes) getters from an object. I'm adding a getter like this:
Object.defineProperty(obj, 'text', {
get: getter
})
obj.text should only be evaluated the first time it's accessed, and the calculated value cached for subsequent calls.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get shows how to implement a smart getter like this:
get notifier() {
delete this.notifier;
return this.notifier = document.getElementById('bookmarked-notification-anchor');
}
I can't use delete this.text inside my getter function however. What I've found is, that this is the prototype of the Object rather than the instance - is that correct? And if so, how can I delete the getter of the instance and replace it with the calculated value?
edit:
As per the comments, the getter and object looks something like this:
var obj = {}
obj.value = '2018-04-21T12:00:00Z000'
Object.defineProperty(obj, 'text', {
get: function () {
delete this.text // doesn't seem to work
if (this.value == null) return ''
var text = this.value.split('T')[0].split('-').reverse().join('.')
this.text = text
return text // return this.text ends in Maximum call stack size exceeded
}
})
You need to make the property configurable so that you can delete it:
var obj = {value: '2018-04-21T12:00:00Z000'};
Object.defineProperty(obj, 'text', {
get: function () {
delete this.text
if (this.value == null) return ''
var text = this.value.split('T')[0].split('-').reverse().join('.')
console.log("updating")
this.text = text
return text
},
configurable: true
//^^^^^^^^^^^^^^^^^^
});
console.log("first access");
console.log(obj.text);
console.log("second access");
console.log(obj.text);
Apart from that, if you are having issues with properties inherited from a prototype object, you cannot delete it but need to use defineProperty to shadow it.
What I've found is, that this is the prototype of the Object rather than the instance...
Not in the code you've shown, not unless you're calling it oddly.
One way to do it is to use Object.defineProperty to redefine the property.
So for instance, if you're doing this on a one-off object:
var obj = {
get notifier() {
var value = Math.random();
console.log("Getter called");
Object.defineProperty(this, "notifier", {
value: value
});
return value;
}
};
console.log("First use");
console.log(obj.notifier);
console.log("Second use");
console.log(obj.notifier);
Or if it's not a one-off:
function Maker() {
}
Object.defineProperty(Maker.prototype, "notifier", {
get: function() {
var value = Math.random();
console.log("Getter called");
Object.defineProperty(this, "notifier", {
value: value
});
return value;
},
configurable: true
});
var obj = new Maker();
console.log("First use");
console.log(obj.notifier);
console.log("Second use");
console.log(obj.notifier);
I've stuck to ES5-level stuff above since you didn't seem to be using any ES2015+ features.
I am trying you get a better understanding of JavaScript, especially the prototype functionality. I am having trouble with this case:
I am trying to define a function someObject with a type function so that it will behave like the following:
var myTestObject = someObject();
If I call:
myTestObject() ===> "The object is initailType"
and then when this is called
myTestObject.type() ===> "InitialType"
Then if I make this call
myTestObject.type("newtype")
myTestObject.type() ===> "newType"
A call to
myTestObject() ===> "The Object is newType".
I have tried both this How does JavaScript .prototype work?
and this How do you create a method for a custom object in JavaScript?
,but I am getting several different errors depending on how it is implemented, mostly this though (Uncaught TypeError: Object myTestObject has no method 'type'). I feel like I am making this harder then it should be.
edit: more code.
function box(){
var _current = "initialType"
Object.defineProperty(this, "current", {
get: function(){return _current;},
set: function(value){
if(arguments.length === 1){
_current = value;
} }
})
return "The Object is " + this.type(this.current)
}
box.prototype.type = function(newValue){
var type = null;
if(arguments.length == 0){
type = "initialType";
}else {
type = newValue
}
return type
}
I would use something like this:
function Box(){}
Box.prototype.type = "initialType";
Box.prototype.toString = function() {
return "The Object is " + this.type + ".";
};
And use it like this:
var b = new Box();
b.type; // "initialType"
b + ''; // "The Object is initialType."
b.type = 'otherType'; // "otherType"
b.type; // "otherType"
b + ''; // "The Object is otherType."
This does what you've asked, but I don't understand what you want to do with the prototype, so this code doesn't use that. For example, the sample code doesn't use new, so the return value of someObject won't use its prototype.
function someObject()
{
var currentType = "initailType";
var formatter = function() {
return "The object is " + currentType;
};
formatter.type = function(value) {
if (arguments.length == 0) {
return currentType;
} else {
currentType = value;
}
};
return formatter;
}
var myTestObject = someObject();
myTestObject(); // => "The object is initailType"
myTestObject.type(); // => "initialType"
myTestObject.type("newType");
myTestObject.type(); // => "newType"
myTestObject(); // => "The object is newType".
see demo
Edit: example using prototype and new.
function Box() { // class name starts with a capital letter
this._type = "initialType"; // set up default values in constructor function
} // no "return" in constructor function, using "new" handles that
Box.prototype.type = function(value) { // adding method to the prototype
if (arguments.length == 0) { // magic arguments local variable...
return this._type; // initially returns the value set in the constructor
} else {
this._type = value; // update the stored value
}
};
Box.prototype.format = function() // another method on the box, rather than a return from the constructor
{
return "The object is " + this.type(); // could use this._type instead
};
var box = new Box(); // instance variable with lowercase name
console.log(box.type()); // read the default value
console.log(box.format()); // print the message with the initial value of type
box.type("another type"); // set the type property, no return value
console.log(box.format()); // print the new message
I am following a tutorial to create getter and setter in Javascript, I have the code like this:
// Create a new User object that accept an object of properties
function User(properties) {
// Iterate through the properties of the object, and make
// sure it's properly scoped
for (var i in properties) { (function(){
// Create a new getter for the property
this['get' + i] = function() {
return properties[i];
};
// Create a new setter for the property
this['set' + i] = function(val) {
properties[i] = val;
};
})(); }
}
// Create a new User object instance and pass in an object of
// properties to seed it with
var user = new User({
name: 'Bob',
age: 28
});
// Just note that the name property does not exist, as it's private
// within the property object
console.log(user.name == null);
// However, we are able to access its value using the new getname()
// method, that was dynamically generated
console.log(user.getname());
However, console shows error saying user does not have method getname. The code is trying to dynamically generate getter and setter method, it looks ok to me. Any thoughts?
The other answers are correct in that you need to pass i into your anonymous function, but you could also do this with ES5 Getters and Setters:
// Create a new User object that accept an object of properties
function User(properties) {
var self = this; // make sure we can access this inside our anon function
for (var i in properties) {
(function(i) {
Object.defineProperty(self, i, {
// Create a new getter for the property
get: function () {
return properties[i];
},
// Create a new setter for the property
set: function (val) {
properties[i] = val;
}
})
})(i);
}
}
The benefit of using ES5 getters and setters is that now you can do this:
var user = new User({name: 'Bob'});
user.name; // Bob
user.name = 'Dan';
user.name; // Dan
Since they're functions, they modify the passed in properties, not just the object itself. You don't have to use getN or setN anymore, you can just use .N, which makes using it look more like accessing properties on an object.
This approach, however, isn't universally portable (requires IE 9+).
Here's what I'd probably do in practice though:
function User(properties) {
Object.keys(properties).forEach(function (prop) {
Object.defineProperty(this, prop, {
// Create a new getter for the property
get: function () {
return properties[prop];
},
// Create a new setter for the property
set: function (val) {
properties[prop] = val;
}
})
}, this);
}
The above gets rid of your for loop. You're already using an anonymous function, so might as well get the most of it.
Probably a closure issue:
function User(properties) {
// Iterate through the properties of the object, and make
// sure it's properly scoped
for (var i in properties) {
(function(i){
// Create a new getter for the property
this['get' + i] = function() {
return properties[i];
};
// Create a new setter for the property
this['set' + i] = function(val) {
properties[i] = val;
};
}.call(this, i));
}
}
Also, as #Paul pointed out, this was actually referring to the function which was contained in the for loop. Not the User function. I fixed that by using a call and assigning the User this to the function (no need for extra variable).
Your in-loop function is losing the this, do a var t = this; outside loop and refer to t inside. Also, pass i into your function.
function User(properties) {
var t = this, i;
for (i in properties) (function (i) {
t['get' + i] = function () { return properties[i]; };
t['set' + i] = function (val) { properties[i] = val; };
}(i));
}
How could I get a callback whenever new properties are set on a Javascript Object..?
I.e. I don't know which properties are going to be set, but want a callback for any properties that are set.
What I want is
var obj = {};
obj.a = "something"; // triggers callback function
function callback(obj,key,val) {
console.log(key + " was set to " + val + " on ", obj);
}
Is this possible?
You can use the new defineProperty:
function onChange(propertyName, newValue){
...
}
var o = {};
Object.defineProperty(o, "propertyName", {
get: function() {return pValue; },
set: function(newValue) { onChange("propertyName",newValue); pValue = newValue;}});
But depends on the browser version you need to support.
Edit: Added snippet on Jsfiddle, works in IE10. http://jsfiddle.net/r2wbR/
The best thing to do it is a setter function, you don't need a getter,
var obj = {};
setKey(obj, 'a', "something"); // triggers callback function
function setKey(obj, key, val){
obj[key] = val;
callback(obj, key, val);
}
function callback(obj, key, val) {
console.log(key + " was set to " + val + " on ", obj);
}
Do it as generic as you can, don't do a different function for all keys
Try it here
The best idea is to have setter and getter methods. But according to your previous implementation one could still alter your object properties without using setter and getter. Therfore you should make you senstive variables privat. Here is a brief example:
var Person = (function() {
function Person() {};
var _name;
Person.prototype.setName = function(name) {
_name = name;
};
Person.prototype.getName = function() {
return _name;
};
return Person;
})();
var john = new Person();
john.getName();
// => undefined
john.setName("John");
john.getName();
// => "John"
john._name;
// => undefined
john._name = "NOT John";
john.getName();
// => "John"
Unfortunately, JavaScript won't let you know when a property is changed. Many times I've wished it would, but since it wouldn't I've had to find a workaround. Instead of setting the property directly, set it via a setter method which triggers the callback function (and possible use a getter method to access the property too) like this:
function callback(obj,key,val) {
console.log(key + " was set to " + val + " on ", obj);
}
var obj = {};
obj.setA=function(value){
obj.a=value;
callback(obj,'a',value);// triggers callback function
}
obj.getA=function(){
return obj.a;
}
obj.setA("something");
jsFiddle: http://jsfiddle.net/jdwire/v8sJt/
EDIT: Another option if you want to completely prevent changing the property without a callback:
function callback(obj,key,val) {
console.log(key + " was set to " + val + " on ", obj);
}
var obj={};
(function(){
var a=null;
obj.setA=function(value){
a=value;
callback(obj,'a',value);// triggers callback function
}
obj.getA=function(){
return a;
}
})()
console.log("a is "+obj.getA());// a is null
obj.setA("something"); // Set a to something
console.log("a is now "+obj.getA()); // a is now something
obj.a="something else"; // Set obj.a to something else to show how a is only accessible through setA
console.log("a is still "+obj.getA()); // a is still something
jsFiddle: http://jsfiddle.net/jdwire/wwaL2/
I'm reading "Pro JavaScript Techniques" by John Resig, and I'm confused with an example. This is the code:
// Create a new user object that accepts an object of properties
function User( properties ) {
// Iterate through the properties of the object, and make sure
// that it's properly scoped (as discussed previously)
for ( var i in properties ) { (function(){
// Create a new getter for the property
this[ "get" + i ] = function() {
return properties[i];
};
// Create a new setter for the property
this[ "set" + i ] = function(val) {
properties[i] = val;
};
})(); }
}
// Create a new user object instance and pass in an object of
// properties to seed it with
var user = new User({
name: "Bob",
age: 44
});
// Just note that the name property does not exist, as it's private
// within the properties object
alert( user.name == null );
// However, we're able to access its value using the new getname()
// method, that was dynamically generated
alert( user.getname() == "Bob" );
// Finally, we can see that it's possible to set and get the age using
// the newly generated functions
user.setage( 22 );
alert( user.getage() == 22 );
Now running that in the Firebug console (on Firefox 3) throws that user.getname() is not a function. I tried doing this:
var other = User
other()
window.getname() // --> This works!
And it worked!
Why?
Doing:
var me = this;
seems to work a bit better, but when executing "getname()" it returns '44' (the second property)...
Also I find it strange that it worked on the window object without modification...
And a third question, what's the difference between PEZ solution and the original? (He doesn't use an anonymous function.)
I think it's best not to use the new keyword at all when working in JavaScript.
This is because if you then instantiate the object without using the new keyword (ex: var user = User()) by mistake, *very bad things will happen...*reason being that in the function (if instantiated without the new keyword), the this will refer to the global object, ie the window...
So therefore, I suggest a better way on how to use class-like objects.
Consider the following example :
var user = function (props) {
var pObject = {};
for (p in props) {
(function (pc) {
pObject['set' + pc] = function (v) {
props[pc] = v;
return pObject;
}
pObject['get' + pc] = function () {
return props[pc];
}
})(p);
}
return pObject;
}
In the above example, I am creating a new object inside of the function, and then attaching getters and setters to this newly created object.
Finally, I am returning this newly created object. Note that the the this keyword is not used anywhere
Then, to 'instantiate' a user, I would do the following:
var john = user({name : 'Andreas', age : 21});
john.getname(); //returns 'Andreas'
john.setage(19).getage(); //returns 19
The best way to avoid falling into pitfalls is by not creating them in the first place...In the above example, I am avoiding the new keyword pitfall (as i said, not using the new keyword when it's supposed to be used will cause bad things to happen) by not using new at all.
Adapting Jason's answer, it works:
We need to make a closure for the values. Here's one way:
function bindAccessors(o, property, value) {
var _value = value;
o["get" + property] = function() {
return _value;
};
o["set" + property] = function(v) {
_value = v;
};
}
Then the User constructor looks like this:
function User( properties ) {
for (var i in properties ) {
bindAccessors(this, i, properties[i]);
}
}
You probably want something like this, which is more readable (closures are easy to learn once you get some practice):
function User( properties ) {
// Helper function to create closures based on passed-in arguments:
var bindGetterSetter = function(obj, p, properties)
{
obj["get" + p] = function() { return properties[p]; }
obj["set" + p] = function(val) { properties[p]=val; return this; }
};
for (var p in properties)
bindGetterSetter(this, p, properties);
}
I also added "return this;", so you can do:
u = new User({a: 1, b:77, c:48});
u.seta(3).setb(20).setc(400)
I started this post with the sole purpose of learning why that things happened, and I finally did. So in case there's someone else interested in the "whys", here they are:
Why does 'this' changes inside the anonymous function?
A new function, even if it is an anonymous, declared inside an object or another function, always changes the scope, in this case returning to the global scope (window).
Solution: all stated in the post, I think the clearer is executing the anonymous function with .call(this).
Why does getname() always return the age?
While the anonymous function gets executed right away, the getters/setters get executed for the first time when they are called. In that moment, the value of i will always be the last, because it has already iterated for all the properties... and it will always return properties[i] which is the last value, in this case the age.
Solution: save the i value in a variable like this
for ( i in properties ) { (function(){
var j = i
// From now on, use properties[j]
As written in the OP, this in the loop is not referring to the User object as it should be. If you capture that variable outside the loop, you can make it work:
function User( properties ) {
// Iterate through the properties of the object, and make sure
// that it's properly scoped (as discussed previously)
var me = this;
for ( i in properties ) { (function(){
// Create a new getter for the property
me[ "get" + i ] = function() {
return properties[i];
};
// Create a new setter for the property
me[ "set" + i ] = function(val) {
properties[i] = val;
};
// etc
I just modified the code a bit like this.. This one should work.. This is same as setting me=this; But a closure is required to set the value of each property properly, else the last value will be assigned to all properties.
// Create a new user object that accepts an object of properties
var User = function( properties ) {
// Iterate through the properties of the object, and make sure
// that it's properly scoped (as discussed previously)
var THIS = this;
for ( var i in properties ) { (function(i){
// Create a new getter for the property
THIS[ "get" + i ] = function() {
return properties[i];
};
// Create a new setter for the property
THIS[ "set" + i ] = function(val) {
properties[i] = val;
};
})(i); }
}
// Create a new user object instance and pass in an object of
// properties to seed it with
var user = new User({
name: "Bob",
age: 44
});
// Just note that the name property does not exist, as it's private
// within the properties object
alert( user.name == null );
// However, we're able to access its value using the new getname()
// method, that was dynamically generated
alert( user.getname() == "Bob" );
// Finally, we can see that it's possible to set and get the age using
// the newly generated functions
user.setage( 22 );
alert( user.getage() == 22 );
Maybe the variable i is "closured" with the last value in the iteration ("age")? Then all getters and setters will access properties["age"].
I found something that seems to be the answer; it’s all about context. Using the anonymous function inside the for loop, changes the context, making 'this' refer to the window object. Strange isn't it?
So:
function User(properties) {
for (var i in properties) {
// Here this == User Object
(function(){
// Inside this anonymous function, this == window object
this["get" + i] = function() {
return properties[i];
};
this["set" + i] = function(val) {
properties[i] = val;
};
})();
}
}
I don't know why that function changes the context of execution, and I'm not sure it should do that. Anyway, you can test it running the code there and trying window.getname(). It magically works! :S
The solution, as stated before, is changing the context. It can be done like J Cooper said, passing the variable 'me' and making the function a closure or you can do this:
(function(){
// Inside this anonymous function this == User
// because we called it with 'call'
this[ "get" + i ] = function() {
return properties[i];
};
this["set" + i] = function(val) {
properties[i] = val;
};
}).call(this);
Anyway, I'm still getting 44 when running 'getname'... What could it be?