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 pro­duc­tion-ready code at the moment, so some of the cool­er recent devel­op­ments in JavaScript have passed me by. In this post I want to address that with a look at a cou­ple of nice new(-ish) fea­tures: muta­tion observers and object observers.

I remem¬≠ber read¬≠ing about muta¬≠tion observers a lit¬≠tle while ago, but didn‚Äôt pay them too much atten¬≠tion as they didn‚Äôt have broad brows¬≠er sup¬≠port and weren‚Äôt imme¬≠di¬≠ate¬≠ly use¬≠ful to me. When I recent¬≠ly saw object observers land in Chrome (36) Beta, I realised that I should go back and learn about them. So I did.

Mutation Observers

Muta¬≠tion observers watch for changes in an ele¬≠ment ‚ÄĒ changes to an attribute, to char¬≠ac¬≠ter data, or to child nodes ‚ÄĒ then report back on the change. I‚Äôm sure I‚Äôll get told off for say¬≠ing this, but they‚Äôre kind of like event lis¬≠ten¬≠ers, only specif¬≠i¬≠cal¬≠ly for changes to an element.

There are two steps to using muta¬≠tion observers: first, cre¬≠ate the observ¬≠er and define what it should do when it observes a change; and sec¬≠ond, ini¬≠ti¬≠ate the observ¬≠er on the ele¬≠ment that might change. The observ¬≠er will then mon¬≠i¬≠tor the DOM for changes to the ele¬≠ment, and fire the call¬≠back func¬≠tion when the change is observed. 

You cre­ate a new observ­er using the MutationObserver con­struct, which requires a call­back func­tion as an argu­ment. The call­back returns an array of objects with infor­ma­tion about the muta­tion. This code exam­ple 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 observ­er which muta­tion types it should watch out for; in this exam­ple, I’m con­fig­ur­ing the observ­er to observe changes in attrib­ut­es, to report the val­ues of those attrib­ut­es before they changed, and to watch all child elements:

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

The next step is to ini¬≠ti¬≠ate the obser¬≠va¬≠tion with the observe() method, pass¬≠ing in two argu¬≠ments: the ele¬≠ment to be watched, and the con¬≠fig¬≠u¬≠ra¬≠tion para¬≠me¬≠ters defined above. It looks like this:

observer.observe(el, config);

Now the observ¬≠er will fire the call¬≠back when¬≠ev¬≠er I make a change to the attribute of my tar¬≠get ele¬≠ment. For exam¬≠ple, if I update the classList of the ele¬≠ment, like so:

el.classList.add('bar');

The observ¬≠er will note this change to the class attribute and, when it‚Äôs able (it runs asyn¬≠chro¬≠nous¬≠ly), run the call¬≠back func¬≠tion. This will, in turn, return a result that looks some¬≠thing like this:

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

This tells me the type of muta­tion (attrib­ut­es), the attribute that was changed (class) and the val­ue of the attribute before the change (foo). Depend­ing on the type of change you want to observe, these results will be quite different.

I’ve made a sim­ple muta­tion observers demo on JSFid­dle. Results are logged to the con­sole. You’ll need IE11 or any oth­er mod­ern browser.

Muta¬≠tion observers are super-use¬≠ful if you‚Äôre writ¬≠ing script that makes fre¬≠quent changes to the DOM, and they aren‚Äôt oner¬≠ous to per¬≠for¬≠mance as they run asyn¬≠chro¬≠nous¬≠ly. There‚Äôs a more com¬≠plete expla¬≠na¬≠tion of muta¬≠tion observers on MDN. Imple¬≠men¬≠ta¬≠tion is sol¬≠id and sta¬≠ble in Chrome and Fire¬≠fox, and in Safari 6+ and IE11+.

By pure serendip¬≠i¬≠ty, Addy Osmani wrote a great intro¬≠duc¬≠tion to muta¬≠tion observers last week.

Object Observers

Like muta¬≠tion observers, object observers watch for changes; but where the for¬≠mer watch ele¬≠ments in the DOM, the lat¬≠ter are focused sole¬≠ly on JavaScript objects. The basic syn¬≠tax is quite sim¬≠i¬≠lar, using the observe() method, but this time applied to the Object wrap¬≠per. The method requires two argu¬≠ments: the object to be observed, and the call¬≠back func¬≠tion that runs when a change to the object takes place.

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

Now it watch­es for changes. For exam­ple, you might add a new prop­er­ty to the object:

myObj.foo = 'bar';

When this hap­pens, the call­back func­tion is fired and returns an object that tells you the name of the prop­er­ty that changed, the type of change (add, update, etc), the pre­vi­ous val­ue if a val­ue were updat­ed, and an object con­tain­ing the updat­ed object.

I‚Äôve put an object observ¬≠er demo on JSFid¬≠dle ‚ÄĒ the results are logged to the con¬≠sole. As of right now, you‚Äôll need Chrome Beta (36) to see this working.

This might be handy for reg¬≠u¬≠lar use, but it becomes down¬≠right essen¬≠tial for two-way data bind¬≠ing ‚ÄĒ if you use MVC frame¬≠works such as Angu¬≠lar¬≠JS or Back¬≠bone, native data bind¬≠ing with Object.observe() is going to give you a huge per¬≠for¬≠mance boost.

Addy Osmani (again) wrote a great intro¬≠duc¬≠tion to Object.observe() which helps to put it into full con¬≠text, and is high¬≠ly rec¬≠om¬≠mend¬≠ed. Sup¬≠port should land in Chrome 36; there is an open issue for Fire¬≠fox sup¬≠port, and IE says Object.observe() is ‚Äėunder con¬≠sid¬≠er¬≠a¬≠tion‚Äô.

Comments are closed.