The following code utilize DOM Mutation Event DOMNodeInserted
to detect the existence of the body
element and wrap its innerHTML
into a wrapper.
<!DOCTYPE html>
<html lang="en">
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<script>
function DOMmanipulation() {
if (document.body) {
document.removeEventListener('DOMNodeInserted', DOMmanipulation);
// DOM manipulation start
document.body.innerHTML = '<div class="wrapper">' + document.body.innerHTML + '</div>';
// DOM manipulation end
}
}
document.addEventListener('DOMNodeInserted', DOMmanipulation);
</script>
</head>
<body>
<p>Lorem ipsum dolor sit amet.</p>
</body>
</html>
And despite the success of the wrapping, there is an error shows that a node was not found. This answer of a question explained that it is because when jQuery had been loaded, it added a div
element into the body to do some tests, but it failed to remove that div
element because that element has been wrapped into the wrapper so that it's not a child element of body anymore.
The above experiment tells us that DOMNodeInserted
event is faster than jQuery's tests because jQuery's test element (div
) got wrapped before it can be removed by jQuery.
Now the following code can achieve the same manipulation, and it's using the newly introduced DOM Mutation Observers. As of this time (2012-07-11), it works only on Chrome 18 and higher.
<!DOCTYPE html>
<html lang="en">
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<script>
var observer = new WebKitMutationObserver(function() {
if (document.body) {
observer.disconnect();
// DOM manipulation start
document.body.innerHTML = '<div class="wrapper">' + document.body.innerHTML + '</div>';
// DOM manipulation end
}
});
observer.observe(document, { subtree: true, childList: true });
</script>
</head>
<body>
<p>Lorem ipsum dolor sit amet.</p>
</body>
</html>
This codes didn't produce any error. That means jQuery is faster than DOM Mutation Observers, so it was able to remove its test element (div
) before that element can be wrapped into the wrapper.
From the above two experiments, we find that when it comes to execution speed:
- DOM Mutation Events > jQuery's tests
- jQuery's tests > DOM Mutation Observers
Can this result appropriately prove that DOM Mutation Observers is slower than DOM Mutation Events?
DOM Mutation Observers, are not intended to be faster than DOM Mutation Events. Rather they are intended to be more efficient and safer.
The basic gist of the difference is that DOM Mutation Events fire whenever there is a change. So this code for example would create a callback loop, that will ultimately crash the browser.
document.addEventListener('DOMNodeInserted', function() {
var newEl = document.createElement('div');
document.body.appendChild(newEl);
});
The fact that they are called in this fashion and so often also has a significant effect on the browser, as it forces an interrupt between the browsers recalculate style, reflow and repaint cycle or worse forces the browser to recalculate styles, reflow and repaint on every callback. The problem is further exasperated by the fact that other code maybe executing that makes further changes to the DOM, which will continue to be interrupted by your callback.
What's more is that because events propagate in the same way as normal DOM Events, you're going to start hearing changes on elements that you might not care about or didn't account for in your code. So the whole mechanism of DOM Mutation Events can become troublesome to manage fairly quickly.
DOM Mutation Observers counteract these problems by, as the name suggests observing changes to the DOM and providing you with a report of all the changes that took place from from the start of the change. This is a much better situation to be in as it allows the browsers to notify you at a time that makes sense, for example when the document is idle and all other JavaScript that could make further changes has finished executing, or before the browser restarts the recalc / repaint cycle, so it can apply any changes you make, without having to repeat the cycle shortly after.
It also makes it easier for you to manage, because you can scan through all the changed elements to find what you're looking for, instead of writing lots of case handling code for stuff you don't care about, as was the situation with Mutation Events. And more importantly its only going to call it once, so you don't need to worry that any further changes are going to effect the elements i.e. they are no longer in changing state, they have changed.
So in answer to your question, DOM Mutation Observers are slower because they waited for jQuery to finish its manipulation of the DOM before it notified you of what jQuery changed. Which for the reason explained above and your example, proves it is safer more efficient solution ( you no longer cause an error), and you didn't really care that jQuery added something to the DOM because it would have removed it shortly after. With Observers you would have received a report detailing the jQuery element being added and removed.
This is still a bit troublesome however because you have to figure out what actually happened by matching up elements with all the changes that took place. The reality is that as far as you're concerned nothing happened ( the same element was added and removed ) so nothing has actually changed in the structure of the DOM. To help with this there is a little library called MutationSummary:
http://code.google.com/p/mutation-summary/
That calculates the net effect of the changes and only calls your callback passing in those changes. So in your case your callback would not have been called at all, because the net effect of the change was zero.
E.g. for the following you will only get one change. The body style was changed to left: 1000px. Even though I changed it in 1000 increments. The net effect of the change is only the difference between its initial value and its final one.
function moveBody() {
for (var i = 0; i < 1000; i++) document.body.style.left = i + 'px';
}
moveBody();
The simple answer is that mutation observers are asynchronous. They are sent whenever the engine feels like it, some time after the change has taken place and in some cases after a great many changes have taken place. That may be long after a DOM mutation event listener would have notified you, but meanwhile the engine has been free to get its work done without having to constantly create and bubble events for every piddling DOM change.