Voyeurism: Mutation and Object Observers

Warning This article was written over six months ago, and may contain outdated information.

I don’t write much in the way of production-ready code at the moment, so some of the cooler recent developments in JavaScript have passed me by. In this post I want to address that with a look at a couple of nice new(-ish) features: mutation observers and object observers.

I remember reading about mutation observers a little while ago, but didn’t pay them too much attention as they didn’t have broad browser support and weren’t immediately useful to me. When I recently saw object observers land in Chrome (36) Beta, I realised that I should go back and learn about them. So I did.

Mutation Observers

Mutation observers watch for changes in an element — changes to an attribute, to character data, or to child nodes — then report back on the change. I’m sure I’ll get told off for saying this, but they’re kind of like event listeners, only specifically for changes to an element.

There are two steps to using mutation observers: first, create the observer and define what it should do when it observes a change; and second, initiate the observer on the element that might change. The observer will then monitor the DOM for changes to the element, and fire the callback function when the change is observed.

You create a new observer using the MutationObserver construct, which requires a callback function as an argument. The callback returns an array of objects with information about the mutation. This code example shows each object in the array logged to the console:

var observer = new MutationObserver( function (mutations) {
  mutations.forEach( function (mutation) {
    console.log(mutation);
  });
});

You can inform the observer which mutation types it should watch out for; in this example, I’m configuring the observer to observe changes in attributes, to report the values of those attributes before they changed, and to watch all child elements:

var config = {
  attributes: true,
  attributeOldValue: true,
  subtree: true
};

The next step is to initiate the observation with the observe() method, passing in two arguments: the element to be watched, and the configuration parameters defined above. It looks like this:

observer.observe(el, config);

Now the observer will fire the callback whenever I make a change to the attribute of my target element. For example, if I update the classList of the element, like so:

el.classList.add('bar');

The observer will note this change to the class attribute and, when it’s able (it runs asynchronously), run the callback function. This will, in turn, return a result that looks something like this:

{
  type: "attributes",
  attributeName: "class",
  oldValue: "foo"
}

This tells me the type of mutation (attributes), the attribute that was changed (class) and the value of the attribute before the change (foo). Depending on the type of change you want to observe, these results will be quite different.

I’ve made a simple mutation observers demo on JSFiddle. Results are logged to the console. You’ll need IE11 or any other modern browser.

Mutation observers are super-useful if you’re writing script that makes frequent changes to the DOM, and they aren’t onerous to performance as they run asynchronously. There’s a more complete explanation of mutation observers on MDN. Implementation is solid and stable in Chrome and Firefox, and in Safari 6+ and IE11+.

By pure serendipity, Addy Osmani wrote a great introduction to mutation observers last week.

Object Observers

Like mutation observers, object observers watch for changes; but where the former watch elements in the DOM, the latter are focused solely on JavaScript objects. The basic syntax is quite similar, using the observe() method, but this time applied to the Object wrapper. The method requires two arguments: the object to be observed, and the callback function that runs when a change to the object takes place.

Object.observe(myObj, function (changes) {
  changes.forEach( function (change) {
    console.log(change);
  });
});

Now it watches for changes. For example, you might add a new property to the object:

myObj.foo = 'bar';

When this happens, the callback function is fired and returns an object that tells you the name of the property that changed, the type of change (add, update, etc), the previous value if a value were updated, and an object containing the updated object.

I’ve put an object observer demo on JSFiddle — the results are logged to the console. As of right now, you’ll need Chrome Beta (36) to see this working.

This might be handy for regular use, but it becomes downright essential for two-way data binding — if you use MVC frameworks such as AngularJS or Backbone, native data binding with Object.observe() is going to give you a huge performance boost.

Addy Osmani (again) wrote a great introduction to Object.observe() which helps to put it into full context, and is highly recommended. Support should land in Chrome 36; there is an open issue for Firefox support, and IE says Object.observe() is ‘under consideration’.

Comments are closed.