0

I have an object like

const obj = { field1: obj1, field2: obj2 }

and now I'd like to run a function when anything in obj was changed:

function objChanged() { ... }

// decorate obj somehow ...

obj.field3 = data; // objChanged should be called (Proxy can see it)
obj.field1.val = data; //objChanged should be called (Proxy can't see it?)

AFAIK there is a MutationObserver which works only for DOM and Proxy which intercepts only own properties, right?

I do not own obj1 so I can not change it. Is there a way to achieve this functionality?

Jan Turoň
  • 31,451
  • 23
  • 125
  • 169

2 Answers2

1

Following the piece of code will listen to object property you can iterate over object properties to listen all. I am curious, what are you trying to achieve?

const dog = { bark: true };

function Observer(o, property) {
  var _this = this;
  this.observers = [];

  this.Observe = function (notifyCallback) {
    _this.observers.push(notifyCallback);
  };

  Object.defineProperty(o, property, {
    set: function (val) {
      _this.value = val;
      for (var i = 0; i < _this.observers.length; i++) {
        _this.observers[i](val);
      }
    },
    get: function () {
      return _this.value;
    },
  });
}

const observer = new Observer(dog, "bark");

observer.Observe(function (value) {
  l("Barked");
});

dog.bark = true;
dog.bark = true;
dog.bark = true;
dog.bark = true;
Orgil
  • 691
  • 7
  • 16
  • 1
    I work with PWA which need some JS variables persistent on the client side, so I try to sync changes with localStorage (which stores only strings) or possibly indexedDB (which is asynchronous) and to minimize r/w operations (do not sync objects which were not changed) to make it less resource consuming. Anyway, great idea with the Observer, thanks! – Jan Turoň May 17 '22 at 17:11
  • As I tested your solution, I found issues which needed a different approach, see my answer. – Jan Turoň May 17 '22 at 21:32
1

Orgil's answer works only with a single property that needs to be known and encoded. I wanted a solution which works for all properties, including later added. Inspired by his idea to create an observing object, I created a dynamic Proxy that adds another Proxies when needed.

In the following code dog1 serves as proxy: setting its properties modifies the original dog object and logs the assigned value to console.

function AssignProxy(o, fn, path) {
    var tree = {};
    if(!path) path = "obj";
    return new Proxy(o, {
        get: (_, prop) => {
            if(typeof o[prop] != "object") return o[prop];
            if(tree[prop] === undefined) tree[prop] = AssignProxy(o[prop], fn, `${path}.${prop}`);
            return tree[prop];
        },
        set: (_, prop, val) => fn(o[prop] = val, prop, o, path) || 1
    });
}


/****** TEST *******/

const dog = {
    sounds: {},
    name: "Spike"
};

let callback = (val, prop, o, path) => console.log(`assigning ${path}.${prop} to ${val}`)
const dog1 = AssignProxy(dog, callback, "dog1");

dog1.name = "Tyke"; // overwrite property
dog1.age = 4; // create a property
dog1.sounds.howl = "hoooooowl"; // create a deep property
dog1.sounds.howl = {text: "hoowl", pitch: 5000}; // overwrite the deep property
var howl = dog1.sounds.howl; // access by reference
howl.pitch = 6000; // overwrite later added property
console.log(dog); // verify the original object
Jan Turoň
  • 31,451
  • 23
  • 125
  • 169