How to use MutationObserver with an Object?

2.7k Views Asked by At

I have the following object:

mind = {
    queries: [],
    actions: []
};

and I update queries and actions according to another function.

I wanted to detect every time they're being updated and changed, and i've heard about MutationObserver, so I tried to call it:

var muob = (window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver);
var ob = new muob(function(m) {
    console.log('It works!');
});
ob.observe(mind, { subtree: true });

But it doesn't work. I get in return:

Uncaught TypeError: Failed to execute 'observe' on 'MutationObserver': parameter 1 is not of type 'Node'.

What's wrong with my code?

2

There are 2 best solutions below

3
On BEST ANSWER

MutationObserver is only something that works for DOM elements, not objects:

var ob = new MutationObserver(function(m) {
    console.log('It works!');
});
ob.observe(mind, { childList: true });

mind.textContent = 'foo';
<div id="mind"></div>

For what you're doing, you can make the queries and actions properties have methods to update the arrays instead, eg:

const mind = {
  _queries: [],
  _actions: [],
  queries: {
    push(...args) {
      console.log('Push detected');
      mind._queries.push(...args);
    },
    get() {
      return mind._queries;
    }
  },
  actions: {
    push(...args) {
      console.log('Push detected');
      mind._actions.push(...args);
    },
    get() {
      return mind._actions;
    }
  }
};

mind.queries.push('foo');
console.log(mind.queries.get());

Or, using a Proxy:

const handler = {
  set(obj, prop, newVal) {
    console.log('Change detected');
    return obj[prop] = newVal;
  },
  get(obj, prop) {
    return obj[prop];
  }
};

const mind = {
  queries: new Proxy([], handler),
  actions: new Proxy([], handler),
};

mind.queries.push('foo');
console.log(mind.queries);

(the above snippet logs Change detected twice because it has to update both the 0 property on the array and change the array's .length)

Still, this is pretty odd - it would be much more elegant if, at the location in the code where you change the array, you also call another function (the It works! part) to indicate an update has occurred.

0
On

MutationObserver is for DOM elements, not JavaScript objects.

There is no equivalent for JavaScript objects,¹ but you can use a combination of Proxy objects and accessor properties to get a notification of any change to those arrays. You'd use the Proxy to know when the arrays were modified, and by making x and y accessor properties, you could know when they were changed (and use that as the opportunity to wrap them in proxies).

Here's a rough sketch:

const mind = (() => {
    function wrap(array) {
        return new Proxy(array, {
            set(target, propName, value, receiver) {
                beforeChange(target, propName, value, receiver);
                const result = Reflect.set(target, propName, value);
                afterChange(target, propName, value, receiver);
                return result;
            }
            // ...you may want other traps here...
        });
    }

    function beforeChange(target, name, value, receiver) {
        console.log("beforeChange", name, value);
    }

    function afterChange(target, name, value, receiver) {
        console.log("afterChange", name, value);
    }

    let queries = wrap([]);
    let actions = wrap([]);
    return {
        get queries() {
            return queries;
        },
        set queries(value) {
            beforeChange(queries, "*queries*", value);
            queries = wrap(value);
            afterChange(queries, "*queries*", value);
        },
        get actions() {
            return queries;
        },
        set queries(value) {
            beforeChange(queries, "*actions*", value);
            queries = wrap(value);
            afterChange(queries, "*actions*", value);
        }
    };
})();

mind.queries.push(1);
mind.actions.push("two");
console.log(mind.actions);
mind.actions[0] = "TWO";
console.log(mind.actions);
mind.queries = [];
mind.queries[10] = "ten";
console.log(mind.queries);
.as-console-wrapper {
    max-height: 100% !important;
 }


¹ For a brief time there was going to be Object.observe, but it was abandoned in favor of Proxy.