15

Is it possible to listen to property changes without the use of Proxy and setInterval?

For common objects you could use the function below but that works for all existing properties but doesn't work for any properties that might get added after the wrapping.

function wrap(obj) {
  var target = {};
  Object.keys(obj).forEach(function(key) {
    target[key] = obj[key];
    Object.defineProperty(obj, key, {
      get: function() {
        console.log("Get");
        return target[key];
      },
      set: function(newValue) {
        console.log("Set");
        target[key] = newValue;
      }
    });
  });
}

var obj = {
  a: 2,
  b: 3
};
wrap(obj);

obj.a; // Get
obj.a = 2; // Set
obj.b; // Get
obj.b = 2; // Set
obj.c = 2; // Nothing
obj.c; // Nothing

If the object is an array you could also listen to the length property and reset all the get and set functions when it's changed. This is obviously not very efficient as it changes the properties of each element whenever an element is added or removed.

So I don't think that Object.defineProperty is the answer.

The reason I don't want to use setInterval is because having big intervals will make the wrapping unreliable whereas having small intervals will have a big impact on the efficiency.

nick zoum
  • 7,216
  • 7
  • 36
  • 80
  • 5
    No, you can't do that in ES5. – Bergi Mar 26 '19 at 14:57
  • 1
    @Bergi How do single page applications like angular do that? `setInterval`? – nick zoum Mar 26 '19 at 15:02
  • 2
    In the old days? Yes, I think so, a timeout or explicitly triggering a dirty check. – Bergi Mar 26 '19 at 15:03
  • TBH I'd rather have an AngularJS expert confirm my thoughts before making it an answer – Bergi Mar 26 '19 at 17:24
  • not an expert, but angular has a digest cycle that will check for changes everytime some "event" occurs, like a click, an `` change, some http callback, etc. – Guillaume Apr 05 '19 at 07:57
  • 3
    you basically need to use the `$http` provided by angular, which wraps http calls to do the digest at the end. They also have a `$timeout` and `$interval` for similar with `setTimeout` and `setInterval`. When you create a framework like this, you need to use all methods provided in framework, or you can call `$scope.$digest()` to trigger the digest manually if you do something outside of "angular" – Guillaume Apr 05 '19 at 08:40
  • 4
    Angularjs follows something called a `dirty check` which is basically iterating through their list of `watchers` from the `rootScope` to all the the children scopes and comparing if the `scope` value has changed. This happens constantly in the background and each run is called a `digest cycle`. Their internal structure can get really complex, hence it's always recommended to use their wrapper methods (ex: `$timeout instead of setTimeout`) – varun agarwal Apr 09 '19 at 06:32

1 Answers1

5

Sadly no, that's why Proxies were such a big thing. There is no other way, for now, to trigger code when a property is added to an object than Proxy.

As you say, you can use Object.defineProperty or var a = { get x() {...}, set x(value) {...} } but not detect new properties.


Most frameworks rely on dirty-check: comparing objects on a giving timing. The timing is where the difference mainly is.

AngularJS (Angular 1.x) gave you special functions for asynchronous operations like $timeout and $http and it's own way to listen to DOM events that will wrap your callbacks and run the check after your code.

Angular (Angular 2 to N) uses Zone.js to create a "Running context" for your code, any asynchronous callback is intercepted by Zone.js. It's basically the same solution as for AngularJS but works automagically.

React does something similar but instead of tracking your variables it runs the renderer and compares if the generated DOM (Virtual DOM) is different.

A. Matías Quezada
  • 1,886
  • 17
  • 34