[javascript] Listening for variable changes in JavaScript

Is it possible to have an event in JS that fires when the value of a certain variable changes? JQuery is accepted.

This question is related to javascript jquery dom-events

The answer is


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.


//ex:
/*
var x1 = {currentStatus:undefined};
your need is x1.currentStatus value is change trigger event ?
below the code is use try it.
*/
function statusChange(){
    console.log("x1.currentStatus_value_is_changed"+x1.eventCurrentStatus);
};

var x1 = {
    eventCurrentStatus:undefined,
    get currentStatus(){
        return this.eventCurrentStatus;
    },
    set currentStatus(val){
        this.eventCurrentStatus=val;
      //your function();
    }
};

or

/*  var x1 = {
eventCurrentStatus:undefined,
currentStatus : {
    get : function(){
        return Events.eventCurrentStatus
        },
    set : function(status){
        Events.eventCurrentStatus=status;

    },
}*/
console.log("eventCurrentStatus = "+ x1.eventCurrentStatus);
x1.currentStatus="create"
console.log("eventCurrentStatus = "+ x1.eventCurrentStatus);
x1.currentStatus="edit"
console.log("eventCurrentStatus = "+ x1.eventCurrentStatus);
console.log("currentStatus = "+ x1.currentStatus);

or

/* global variable ku*/
    var jsVarEvents={};
    Object.defineProperty(window, "globalvar1", {//no i18n
        get: function() { return window.jsVarEvents.globalvarTemp},
        set: function(value) { window.window.jsVarEvents.globalvarTemp = value; }
    });
    console.log(globalvar1);
    globalvar1=1;
    console.log(globalvar1);

Using Prototype: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty

_x000D_
_x000D_
// Console_x000D_
function print(t) {_x000D_
  var c = document.getElementById('console');_x000D_
  c.innerHTML = c.innerHTML + '<br />' + t;_x000D_
}_x000D_
_x000D_
// Demo_x000D_
var myVar = 123;_x000D_
_x000D_
Object.defineProperty(this, 'varWatch', {_x000D_
  get: function () { return myVar; },_x000D_
  set: function (v) {_x000D_
    myVar = v;_x000D_
    print('Value changed! New value: ' + v);_x000D_
  }_x000D_
});_x000D_
_x000D_
print(varWatch);_x000D_
varWatch = 456;_x000D_
print(varWatch);
_x000D_
<pre id="console">_x000D_
</pre>
_x000D_
_x000D_
_x000D_

Other example

_x000D_
_x000D_
// Console_x000D_
function print(t) {_x000D_
  var c = document.getElementById('console');_x000D_
  c.innerHTML = c.innerHTML + '<br />' + t;_x000D_
}_x000D_
_x000D_
// Demo_x000D_
var varw = (function (context) {_x000D_
  return function (varName, varValue) {_x000D_
    var value = varValue;_x000D_
  _x000D_
    Object.defineProperty(context, varName, {_x000D_
      get: function () { return value; },_x000D_
      set: function (v) {_x000D_
        value = v;_x000D_
        print('Value changed! New value: ' + value);_x000D_
      }_x000D_
    });_x000D_
  };_x000D_
})(window);_x000D_
_x000D_
varw('varWatch'); // Declare_x000D_
print(varWatch);_x000D_
varWatch = 456;_x000D_
print(varWatch);_x000D_
_x000D_
print('---');_x000D_
_x000D_
varw('otherVarWatch', 123); // Declare with initial value_x000D_
print(otherVarWatch);_x000D_
otherVarWatch = 789;_x000D_
print(otherVarWatch);
_x000D_
<pre id="console">_x000D_
</pre>
_x000D_
_x000D_
_x000D_


Easiest way I have found, starting from this answer:

// variable holding your data
const state = {
  count: null,
  update() {
    console.log(`this gets called and your value is ${this.pageNumber}`);
  },
  get pageNumber() {
    return this.count;
  },
  set pageNumber(pageNumber) {
    this.count = pageNumber;
    // here you call the code you need
    this.update(this.count);
  }
};

And then:

state.pageNumber = 0;
// watch the console

state.pageNumber = 15;
// watch the console

If you're using jQuery {UI} (which everyone should be using :-) ), you can use .change() with a hidden <input/> element.


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.


AngularJS (I know this is not JQuery, but that might help. [Pure JS is good in theory only]):

$scope.$watch('data', function(newValue) { ..

where "data" is name of your variable in the scope.

There is a link to doc.


Utils = {
    eventRegister_globalVariable : function(variableName,handlers){
        eventRegister_JsonVariable(this,variableName,handlers);
    },
    eventRegister_jsonVariable : function(jsonObj,variableName,handlers){
        if(jsonObj.eventRegisteredVariable === undefined) {
            jsonObj.eventRegisteredVariable={};//this Object is used for trigger event in javascript variable value changes ku
        }
        Object.defineProperty(jsonObj, variableName , {
                    get: function() { 
                        return jsonObj.eventRegisteredVariable[variableName] },
                    set: function(value) {
                        jsonObj.eventRegisteredVariable[variableName] = value; handlers(jsonObj.eventRegisteredVariable[variableName]);}
                    });
            }

This is what I did: Call JSON.stringify twice and compare the two strings...

Drawbacks:

  • You can only know whether the whole object changes
  • You have to detect changes manually
  • You better have only primitive fields in the object(no properties, no functions...)

I came here looking for same answer for node js. So here it is

const events = require('events');
const eventEmitter = new events.EventEmitter();

// Createing state to watch and trigger on change
let x = 10 // x is being watched for changes in do while loops below

do {
    eventEmitter.emit('back to normal');
}
while (x !== 10);

do {
    eventEmitter.emit('something changed');
}
while (x === 10);

What I am doing is setting some event emitters when values are changed and using do while loops to detect it.


No.

But, if it's really that important, you have 2 options (first is tested, second isn't):

First, use setters and getters, like so:

var myobj = {a : 1};

function create_gets_sets(obj) { // make this a framework/global function
    var proxy = {}
    for ( var i in obj ) {
        if (obj.hasOwnProperty(i)) {
            var k = i;
            proxy["set_"+i] = function (val) { this[k] = val; };
            proxy["get_"+i] = function ()    { return this[k]; };
        }
    }
    for (var i in proxy) {
        if (proxy.hasOwnProperty(i)) {
            obj[i] = proxy[i];
        }
    }
}

create_gets_sets(myobj);

then you can do something like:

function listen_to(obj, prop, handler) {
    var current_setter = obj["set_" + prop];
    var old_val = obj["get_" + prop]();
    obj["set_" + prop] = function(val) { current_setter.apply(obj, [old_val, val]); handler(val));
}

then set the listener like:

listen_to(myobj, "a", function(oldval, newval) {
    alert("old : " + oldval + " new : " + newval);
}

Second, you could put a watch on the value:

Given myobj above, with 'a' on it:

function watch(obj, prop, handler) { // make this a framework/global function
    var currval = obj[prop];
    function callback() {
        if (obj[prop] != currval) {
            var temp = currval;
            currval = obj[prop];
            handler(temp, currval);
        }
    }
    return callback;
}

var myhandler = function (oldval, newval) {
    //do something
};

var intervalH = setInterval(watch(myobj, "a", myhandler), 100);

myobj.set_a(2);

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).


This is an old thread but I stumbled onto second highest answer (custom listeners) while looking for a solution using Angular. While the solution works, angular has a better built in way to resolve this using @Output and event emitters. Going off of the example in custom listener answer:

ChildComponent.html

<button (click)="increment(1)">Increment</button>

ChildComponent.ts

import {EventEmitter, Output } from '@angular/core';

@Output() myEmitter: EventEmitter<number> = new EventEmitter<number>();

private myValue: number = 0;

public increment(n: number){
  this.myValue += n;

  // Send a change event to the emitter
  this.myEmitter.emit(this.myValue);
}

ParentComponent.html

<child-component (myEmitter)="monitorChanges($event)"></child-component>
<br/>
<label>{{n}}</label>

ParentComponent.ts

public n: number = 0;

public monitorChanges(n: number){
  this.n = n;
  console.log(n);
}

This will now update non parent each time the child button is clicked. Working stackblitz


As Luke Schafer's answer (note: this refers to his original post; but the whole point here remains valid after the edit), I would also suggest a pair of Get/Set methods to access your value.

However I would suggest some modifications (and that's why I'm posting...).

A problem with that code is that the field a of the object myobj is directly accessible, so it's possible to access it / change its value without triggering the listeners:

var myobj = { a : 5, get_a : function() { return this.a;}, set_a : function(val) { this.a = val; }}
/* add listeners ... */
myobj.a = 10; // no listeners called!

Encapsulation

So, to guarantee that the listeners are actually called, we would have to prohibit that direct access to the field a. How to do so? Use a closure!

var myobj = (function() { // Anonymous function to create scope.

    var a = 5;            // 'a' is local to this function
                          // and cannot be directly accessed from outside
                          // this anonymous function's scope

    return {
        get_a : function() { return a; },   // These functions are closures:
        set_a : function(val) { a = val; }  // they keep reference to
                                            // something ('a') that was on scope
                                            // where they were defined
    };
})();

Now you can use the same method to create and add the listeners as Luke proposed, but you can rest assured that there's no possible way to read from or write to a going unnoticed!

Adding encapsulated fields programmatically

Still on Luke's track, I propose now a simple way to add encapsulated fields and the respective getters/setters to objects by the means of a simple function call.

Note that this will only work properly with value types. For this to work with reference types, some kind of deep copy would have to be implemented (see this one, for instance).

function addProperty(obj, name, initial) {
    var field = initial;
    obj["get_" + name] = function() { return field; }
    obj["set_" + name] = function(val) { field = val; }
}

This works the same as before: we create a local variable on a function, and then we create a closure.

How to use it? Simple:

var myobj = {};
addProperty(myobj, "total", 0);
window.alert(myobj.get_total() == 0);
myobj.set_total(10);
window.alert(myobj.get_total() == 10);

In my case, I was trying to find out if any library I was including in my project was redefining my window.player. So, at the begining of my code, I just did:

Object.defineProperty(window, 'player', {
  get: () => this._player,
  set: v => {
    console.log('window.player has been redefined!');
    this._player = v;
  }
});

It's not directly possible.

However, this can be done using CustomEvent: https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent

The below method accepts an array of variable names as an input and adds event listener for each variable and triggers the event for any changes to the value of the variables.

The Method uses polling to detect the change in the value. You can increase the value for timeout in milliseconds.

function watchVariable(varsToWatch) {
    let timeout = 1000;
    let localCopyForVars = {};
    let pollForChange = function () {
        for (let varToWatch of varsToWatch) {
            if (localCopyForVars[varToWatch] !== window[varToWatch]) {
                let event = new CustomEvent('onVar_' + varToWatch + 'Change', {
                    detail: {
                        name: varToWatch,
                        oldValue: localCopyForVars[varToWatch],
                        newValue: window[varToWatch]
                    }
                });
                document.dispatchEvent(event);
                localCopyForVars[varToWatch] = window[varToWatch];
            }
        }
        setTimeout(pollForChange, timeout);
    };
    let respondToNewValue = function (varData) {
        console.log("The value of the variable " + varData.name + " has been Changed from " + varData.oldValue + " to " + varData.newValue + "!!!"); 
    }
    for (let varToWatch of varsToWatch) {
        localCopyForVars[varToWatch] = window[varToWatch];
        document.addEventListener('onVar_' + varToWatch + 'Change', function (e) {
            respondToNewValue(e.detail);
        });
    }
    setTimeout(pollForChange, timeout);
}

By calling the Method:

watchVariables(['username', 'userid']);

It will detect the changes to variables username and userid.


A rather simple and simplistic solution is to just use a function call to set the value of the global variable, and never set its value directly. This way you have total control:

var globalVar;

function setGlobalVar(value) {
    globalVar = value;
    console.log("Value of globalVar set to: " + globalVar);
    //Whatever else
}

There is no way to enforce this, it just requires programming discipline... though you can use grep (or something similar) to check that nowhere does your code directly set the value of globalVar.

Or you could encapsulate it in an object and user getter and setter methods... just a thought.


Sorry to bring up an old thread, but here is a little manual for those who (like me!) don't see how Eli Grey's example works:

var test = new Object();
test.watch("elem", function(prop,oldval,newval){
    //Your code
    return newval;
});

Hope this can help someone


The functionality you're looking for can be achieved through the use of the "defineProperty()" method--which is only available to modern browsers:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty

I've written a jQuery extension that has some similar functionality if you need more cross browser support:

https://github.com/jarederaj/jQueue

A small jQuery extension that handles queuing callbacks to the existence of a variable, object, or key. You can assign any number of callbacks to any number of data points that might be affected by processes running in the background. jQueue listens and waits for these data you specify to come into existence and then fires off the correct callback with its arguments.


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:

  1. money.beforeSet(newValue, oldValue);
  2. money.beforeChange(newValue, oldValue); (Will be skipped if its value not changed)
  3. money.val = newValue;
  4. money.afterChange(newValue, oldValue); (Will be skipped if its value not changed)
  5. money.afterSet(newValue, oldValue);

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.


Recently found myself with the same issue. Wanted to listen for on change of a variable and do some stuff when the variable changed.

Someone suggested a simple solution of setting the value using a setter.

Declaring a simple object that keeps the value of my variable here:

var variableObject = {
    value: false,
    set: function (value) {
        this.value = value;
        this.getOnChange();
    }
}

The object contains a set method via which I can change the value. But it also calls a getOnChange() method in there. Will define it now.

variableObject.getOnChange = function() {
    if(this.value) {
        // do some stuff
    }
}

Now whenever I do variableObject.set(true), the getOnChange method fires, and if the value was set as desired (in my case: true), the if block also executes.

This is the simplest way I found to do this stuff.


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/


Most of the answers to this question are either outdated, ineffective, or require the inclusion of large bloated libraries:

  • Object.watch and Object.observe are both deprecated and should not be used.
  • onPropertyChange is a DOM element event handler that only works in some versions of IE.
  • Object.defineProperty allows you to make an object property immutable, which would allow you to detect attempted changes, but it would also block any changes.
  • Defining setters and getters works, but it requires a lot of setup code and it does not work well when you need to delete or create new properties.

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:

  1. The Proxy object is not available in older browsers (such as IE11) and the polyfill cannot fully replicate Proxy functionality.
  2. Proxy objects do not always behave as expected with special objects (e.g., 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}}]

Examples related to javascript

need to add a class to an element How to make a variable accessible outside a function? Hide Signs that Meteor.js was Used How to create a showdown.js markdown extension Please help me convert this script to a simple image slider Highlight Anchor Links when user manually scrolls? Summing radio input values How to execute an action before close metro app WinJS javascript, for loop defines a dynamic variable name Getting all files in directory with ajax

Examples related to jquery

How to make a variable accessible outside a function? Jquery assiging class to th in a table Please help me convert this script to a simple image slider Highlight Anchor Links when user manually scrolls? Getting all files in directory with ajax Bootstrap 4 multiselect dropdown Cross-Origin Read Blocking (CORB) bootstrap 4 file input doesn't show the file name Jquery AJAX: No 'Access-Control-Allow-Origin' header is present on the requested resource how to remove json object key and value.?

Examples related to dom-events

Detecting real time window size changes in Angular 4 Does Enter key trigger a click event? What are passive event listeners? Stop mouse event propagation React onClick function fires on render How do you Hover in ReactJS? - onMouseLeave not registered during fast hover over iFrame onload JavaScript event addEventListener, "change" and option selection Automatically pass $event with ng-click? JavaScript click event listener on class