I've been working on a [`MutationObserver` es5 shim][1] and would appreciate some feedback on my technique for identifying changes between a `node` and its `clone` from earlier state. The reason I'm asking for this code review is that this function is likely not perfect (i.e. will have to be updated in the future for missed/incorrect cases) and was the most difficult snippet of code I've ever written. I would like to know if how I've written the code is intuitive/commented enough for fresh eyes and if you have any suggestions for a couple things. Here's the pseudocode I wrote that my actual code is based on: findChildMutations ($node $oldnode, deep): mutations = [] conflicts iterate each $node.childNodes as $node and $oldnode.childNodes as $old: if(sameNode($node, $old)): if(deep): check children of $node and $old resolveConflicts() else: if($node not checked): if($node in $oldnode.childNodes): add addedNode MutationRecord to mutations else if($node at different index in $oldnode.childNodes) add conflict #similarly for $old resolveConflicts() return mutations The goals of the function is to acurately be able to identify `addedNodes` or `removedNodes` in the (optionally deep) `childNodes` of an element. The function should also notice when there is a change in order of nodes. Finally the function should of course be efficient as it will be running more than 20 times a second. [Here's how the function will be used.][2] This is the best I've been able to get the function [and here are my test cases][3]: //findChildMutations: array of mutations so far, element, element clone, bool => array of mutations // dfs comparision search of two nodes // perf and function tests: http://jsbin.com/uhoVibU/4 var findChildMutations = function(target, oldstate, deep) { var mutations = []; var add = function(node) { mutations.push(new MutationRecord({ type: "childList", target: node.parentElement, addedNodes: [node] })); if(deep) $id_kids(node, deep);//ensure children of added ele have ids }; var rem = function(node) { mutations.push(new MutationRecord({ type: "childList", target: deep ? node.parentElement : target,//so target will appear correct on childList - more complicated on subtree removedNodes: [node] })); }; var findMut = function(node, oldnode) { var $kids = node.childNodes; var $oldkids = oldnode.childNodes; var klen = $kids.length; var olen = $oldkids.length; //id to i and j search hash to prevent double checking an element var id; var map = {}; //array of potention conflict hashes var conflicts = []; //offsets //var offset_add = 0;//nodes added since last resolve //we dont have to check added as these are handled before remove var offset_rem = 0;//nodes removed since last resolve /* * There is no gaurentee that the same node will be returned for both added and removed nodes * if the position has been shuffled */ var resolver = function() { var counter = 0;//prevents same conflict being resolved twice var conflict; for (var i = 0, l = conflicts.length-1; i <= l; i++) { conflict = conflicts[i]; //attempt to determine if there was node rearrangement... won't gaurentee all matches //also handles case where added/removed nodes cause nodes to be identified as conflicts if(counter < l && Math.abs(conflict.i - (conflict.j + offset_rem)) >= l) { add($kids[conflict.i]);//rearrangment ie removed then readded rem($kids[conflict.i]); counter++; } else if(deep) {//conflicts resolved - check deep findMut($kids[conflict.i], $oldkids[conflict.j]); } } offset_rem = conflicts.length = 0; }; //iterate over both old and current child nodes at the same time for(var i = 0, j = 0, p; i < klen || j < olen; ) { if(sameNode($kids[i], $oldkids[j])) {//simple expected case if(deep) {//recurse findMut($kids[i], $oldkids[j]); } //resolve conflicts resolver(); i++; j++; } else {//lookahead until they are the same again or the end of children if(i < klen) { id = getId($kids[i]); //check id is in the location map otherwise do a indexOf search if(!has.call(map, id)) {//not already found if((p = findIndex($oldkids, $kids[i], j)) === -1) { add($kids[i]); } else { conflicts.push(map[id] = {//bit dirty i: i, j: p }); } } i++; } if(j < olen) { id = getId($oldkids[j]); if(!has.call(map, id)) { if((p = findIndex($kids, $oldkids[j], i)) === -1) { rem($oldkids[j]); offset_rem++; } else { conflicts.push(map[id] = { i: p, j: j }); } } j++; } } } resolver(); }; findMut(target, oldstate); return mutations; }; Helper functions: //Assigns a unique id to each node to be watched in order to be able to compare cloned nodes //TODO find a cleaner way eg some hash represnetnation var counter = 0; var getId = function($ele) { var id = $ele.nodeType === 3 ? $ele.nodeValue ://text node id is the text content $ele.id || $ele.getAttribute("mut-id") || ++counter; if(id === counter) { $ele.setAttribute("mut-id", id); } return id; }; var sameNode = function(node1, node2) { return node1 && node2 && getId(node1) === getId(node2); }; var findIndex = function(set, node, from) { from = ~~from; for(var i = from,l=set.length; i<l; i++) { if(sameNode(node, set[i])) return i; } return -1; }; //set the ids for all of an elements children var $id_kids = function(ele, deep) { if(ele.nodeType !== 3) { foreach.call(ele.children, function(node) {//only iterate elements not text nodes getId(node); if(deep) $id_kids(node, deep); }); } return ele; }; Questions: 1. Is there a better way to be able to accurately compare a node to its clone than to assign a unique id to each element being watched (see `getId` helper function)? My concern with using `.equalNode` or parsing the HTML is being able to match the node if there are changes and handling two similarly appearing nodes such as a `li`. I wrote a [gist describing the problem][4]. 2. Do you know of any research/papers discussing comparing two trees/graphs? I haven't found much on the topic. 3. Can you give me some feedback and suggestions on my implementation? I'll try and get some rep and add a bounty on this in a couple days. [1]: https://github.com/megawac/MutationObserver.js/tree/subtree [2]: https://github.com/megawac/MutationObserver.js/blob/subtree/MutationObserver.js#L245 [3]: http://jsbin.com/uhoVibU/5 [4]: https://gist.github.com/megawac/8201012