Listening for variable changes in JavaScript

后端 未结 22 2819
自闭症患者
自闭症患者 2020-11-21 06:57

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

相关标签:
22条回答
  • 2020-11-21 07:12

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

    0 讨论(0)
  • 2020-11-21 07:12

    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.

    0 讨论(0)
  • 2020-11-21 07:13

    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}}]
    
    0 讨论(0)
  • 2020-11-21 07:14

    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.

    0 讨论(0)
  • 2020-11-21 07:16

    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/

    0 讨论(0)
  • 2020-11-21 07:22

    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.

    0 讨论(0)
提交回复
热议问题