Is it possible to have an event in JS that fires when the value of a certain variable changes? JQuery is accepted.
Not directly: you need a pair getter/setter with an "addListener/removeListener" interface of some sort... or an NPAPI plugin (but that's another story altogether).
With the help of getter and setter, you can define a JavaScript class that does such a thing.
First, we define our class called MonitoredVariable
:
class MonitoredVariable {
constructor(initialValue) {
this._innerValue = initialValue;
this.beforeSet = (newValue, oldValue) => {};
this.beforeChange = (newValue, oldValue) => {};
this.afterChange = (newValue, oldValue) => {};
this.afterSet = (newValue, oldValue) => {};
}
set val(newValue) {
const oldValue = this._innerValue;
// newValue, oldValue may be the same
this.beforeSet(newValue, oldValue);
if (oldValue !== newValue) {
this.beforeChange(newValue, oldValue);
this._innerValue = newValue;
this.afterChange(newValue, oldValue);
}
// newValue, oldValue may be the same
this.afterSet(newValue, oldValue);
}
get val() {
return this._innerValue;
}
}
Assume that we want to listen for money
changes, let's create an instance of MonitoredVariable
with initial money 0
:
const money = new MonitoredVariable(0);
Then we could get or set its value using money.val
:
console.log(money.val); // Get its value
money.val = 2; // Set its value
Since we have not defined any listeners for it, nothing special happens after money.val
changes to 2.
Now let's define some listeners. We have four listeners available: beforeSet
, beforeChange
, afterChange
, afterSet
.
The following will happen sequentially when you use money.val = newValue
to change variable's value:
Now we define afterChange
listener which be triggered only after money.val
has changed (while afterSet
will be triggered even if the new value is the same as the old one):
money.afterChange = (newValue, oldValue) => {
console.log(`Money has been changed from ${oldValue} to ${newValue}`);
};
Now set a new value 3
and see what happens:
money.val = 3;
You will see the following in the console:
Money has been changed from 2 to 3
For full code, see https://gist.github.com/yusanshi/65745acd23c8587236c50e54f25731ab.
Most of the answers to this question are either outdated, ineffective, or require the inclusion of large bloated libraries:
Today, you can now use the Proxy object to monitor (and intercept) changes made to an object. It is purpose built for what the OP is trying to do. Here's a basic example:
var targetObj = {};
var targetProxy = new Proxy(targetObj, {
set: function (target, key, value) {
console.log(`${key} set to ${value}`);
target[key] = value;
return true;
}
});
targetProxy.hello_world = "test"; // console: 'hello_world set to test'
The only drawbacks of the Proxy
object are:
Proxy
object is not available in older browsers (such as IE11) and the polyfill cannot fully replicate Proxy
functionality.Date
) -- the Proxy
object is best paired with plain Objects or Arrays.If you need to observe changes made to a nested object, then you need to use a specialized library such as Observable Slim (which I have published) which works like this:
var test = {testing:{}};
var p = ObservableSlim.create(test, true, function(changes) {
console.log(JSON.stringify(changes));
});
p.testing.blah = 42; // console: [{"type":"add","target":{"blah":42},"property":"blah","newValue":42,"currentPath":"testing.blah",jsonPointer:"/testing/blah","proxy":{"blah":42}}]
Yes, this is now completely possible!
I know this is an old thread but now this effect is possible using accessors (getters and setters): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Working_with_Objects#Defining_getters_and_setters
You can define an object like this, in which aInternal
represents the field a
:
x = {
aInternal: 10,
aListener: function(val) {},
set a(val) {
this.aInternal = val;
this.aListener(val);
},
get a() {
return this.aInternal;
},
registerListener: function(listener) {
this.aListener = listener;
}
}
Then you can register a listener using the following:
x.registerListener(function(val) {
alert("Someone changed the value of x.a to " + val);
});
So whenever anything changes the value of x.a
, the listener function will be fired. Running the following line will bring the alert popup:
x.a = 42;
See an example here: https://jsfiddle.net/5o1wf1bn/1/
You can also user an array of listeners instead of a single listener slot, but I wanted to give you the simplest possible example.
For those tuning in a couple years later:
A solution for most browsers (and IE6+) is available that uses the onpropertychange event and the newer spec defineProperty. The slight catch is that you'll need to make your variable a dom object.
Full details:
http://johndyer.name/native-browser-get-set-properties-in-javascript/
Please guys remember the initial question was for VARIABLES, not for OBJECTS ;)
in addition to all answers above, I created a tiny lib called forTheWatch.js, that use the same way to catch and callback for changes in normal global variables in javascript.
Compatible with JQUERY variables, no need to use OBJECTS, and you can pass directly an ARRAY of several variables if needed.
If it can be helpful... :
https://bitbucket.org/esabora/forthewatch
Basically you just have to call the function :
watchIt("theVariableToWatch", "varChangedFunctionCallback");
And sorry by advance if not relevant.