-
-
Save ebidel/d923001dd7244dbd3fe0d5116050d227 to your computer and use it in GitHub Desktop.
<!-- | |
This demo shows two ways to detect changes to a DOM node `.textContent`, one | |
using a `MutationObserver` and the other using an ES2015 `Proxy`. | |
From testing, a `Proxy` appears to be 6-8x faster than using a MO in Chrome 50. | |
**Update**: removing the `Proxy` altogether speeds up the MO to be inline with the Proxy. | |
This has something to do with how the browser queues/prioritizes Proxies over MO. | |
Why is this useful? One could imagine creating a primative data-binding system | |
around the `Proxy` approach or using it to [polyfill `Object.observer()`](https://gist.github.com/ebidel/1b553d571f924da2da06). | |
Run it: http://jsbin.com/dukuluwufa/edit?html,output | |
--> | |
<p><button onclick="update()">update .textContent</button></p> | |
<p>[MO]'d node content: <span id="source"></span></p> | |
<p>[Proxy]'d node content: <span id="source-proxied"></span></p> | |
<output></output> | |
<script> | |
let start, finish, start2, finish2; | |
// Watch accesses/sets on a DOM element property. | |
function watchPropsOn(el, prop, callback=null) { | |
return new Proxy(el, { | |
set(target, propKey, value, receiver) { | |
if (prop === propKey) { | |
let finish2 = performance.now(); | |
out.innerHTML += `Proxy took: ${finish2 - start2}ms<br>`; | |
} | |
console.log(`Proxy set .${propKey} to ${value}`); | |
target[propKey] = value; | |
} | |
}); | |
} | |
function observe(target) { | |
// create an observer instance | |
let observer = new MutationObserver(mutations => { | |
let finish = performance.now(); | |
out.innerHTML += `MutationObserver took: ${finish - start}ms<br>`; | |
mutations.forEach(mutation => { | |
if (mutation.addedNodes.length) { | |
console.log(`MutationObserver observed childList as ${target.textContent}`) | |
} | |
}); | |
}); | |
observer.observe(target, {childList: true}); | |
} | |
function update() { | |
out.innerHTML = ''; | |
start = start2 = performance.now(); | |
proxy.textContent = source.textContent = Math.random(); | |
} | |
let source = document.querySelector('#source'); | |
let proxiedSource = document.querySelector('#source-proxied'); | |
let out = document.querySelector('output'); | |
observe(source); // setup MO | |
let proxy = watchPropsOn(proxiedSource, 'textContent'); // setup proxy. | |
</script> |
You're right that removing the Proxy altogether speeds up the MO. It's more in line with the Proxy. 0.20ms MO vs. 0.125ms Proxy on first run.
In Chrome MO doesn't fire if the value is unchanged but in Safari it always does, along with the Proxy. As I recall MO is async and the performance measurement follows after the DOM updates so it makes sense (to me) it would appear slower regardless. Is this valid and accurate? If so it doesn't seem to matter what the performance is but instead which approach better fits the problem being solved. Does this seem a valid conclusion to make?
@ebidel Thanks for this one! Shouts in my README (if you don't mind). #salute 🙏
devpunks/snuggsi@7e4ac9f#diff-04c6e90faac2675aa89e2176d2eec7d8R47
Using them in a textContent
Relay MutationObserver binding. https://github.com/snuggs/snuggsi/blob/master/elements/text-content.js
I think the mutation observer update gets added to the job queue after the proxy update does, which makes it seem like the proxy is faster. For instance, if you remove the proxy altogether and change the
textContent
on source, does the mutationObserver take less time?