diff --git a/README.md b/README.md
index 424fcb2b..5b691d56 100644
--- a/README.md
+++ b/README.md
@@ -566,6 +566,14 @@ Also see [examples/usage-tweenState.js](examples/usage-tweenState.js)
## Changelog
+**1.1.10**
+- better rendering performance: debounce the `render()` function:
+ - `setState()` can be called 1000s of times a second
+ - `setState()` also calls the render() function
+ - `render()` will only update the DOM at 60fps
+- see https://gomakethings.com/debouncing-your-javascript-events/
+- updated README
+
**1.1.9**
- new feature: "middleware"
- define an array of functions as `myComponent.middleware = [ someFunc, otherFunc ]`
diff --git a/dist/component.min.js b/dist/component.min.js
index f5ac82da..fca59fa8 100644
--- a/dist/component.min.js
+++ b/dist/component.min.js
@@ -1 +1 @@
-!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t=t||self).Component=e()}(this,(function(){"use strict";var t=(t,e)=>{const i={cfg:{orig:t},replace(t,e=t){const i=document.createElement("template");i.innerHTML=e;const s=i.content.firstChild.nextElementSibling;s.nodeName===t.nodeName?this.iterate(t,s):t.parentElement.replaceChild(s,t)},iterate(e,i,s){(e||i)&&(this.checkAdditions(e,i,s),e&&i&&e.nodeName!==i.nodeName?this.checkNodeName(e,i):e&&i&&e.nodeName===i.nodeName&&(this.checkTextContent(e,i),3!==e.nodeType&&8!==t.nodeType&&this.checkAttributes(e,i))),e&&i&&(e.childNodes&&i.childNodes?this.cfg.lengthDifferentiator=[...t.childNodes,...i.childNodes]:this.cfg.lengthDifferentiator=null,Array.apply(null,this.cfg.lengthDifferentiator).forEach((t,s)=>{this.cfg.lengthDifferentiator&&this.iterate(e.childNodes[s],i.childNodes[s],e,i)}))},checkNodeName(t,e){const i=e.cloneNode(!0);t.parentElement.replaceChild(i,t)},checkAttributes(t,e){const i=t.attributes||[],s=Object.keys(i).map(t=>i[t]),n=e.attributes||[],h=Object.keys(n).map(t=>n[t]);s.forEach(i=>null!==e.getAttribute(i.name)?t.setAttribute(i.name,e.getAttribute(i.name)):t.removeAttribute(i.name)),h.forEach(i=>t.getAttribute(i.name)!==e.getAttribute(i.name)&&t.setAttribute(i.name,e.getAttribute(i.name)))},checkTextContent(t,e){t.nodeValue!==e.nodeValue&&(t.textContent=e.textContent)},checkAdditions(t,e,i=this.cfg.orig){if(e&&void 0===t){const t=e.cloneNode(!0);3!==i.nodeType&&8!==i.nodeType&&i.appendChild(t)}else t&&void 0===e&&t.parentElement.removeChild(t)}};Object.create(i).replace(t,e)};return function e(i){this.reactive=!0,this.debug=!1,this.scopedCss=!0;var s=this;return this.isNode="undefined"!=typeof process&&null!==process&&null!==process.versions&&null!==process.versions.node,this.state=i,this.view=t=>t,this.middleware=[],this.uid=Math.random().toString(36).split(".")[1],this.isNode||(this.css=document.createElement("style"),document.head.appendChild(this.css)),this.log=[{id:0,state:i,action:"init"}],this.actions=t=>(Object.keys(t).forEach(e=>{if(void 0!==this[e])return!1;this[e]=i=>(this.action=e,t[e](i))}),this),this.freeze=t=>(Object.isFrozen(t)||(Object.keys(t).forEach(e=>this.freeze(t[e])),Object.freeze(t)),t),this.eq=(t,e)=>{const i=Object.keys,s=typeof t;return t&&e&&"object"===s&&s===typeof e?i(t).length===i(e).length&&i(t).every(i=>this.eq(t[i],e[i])):String(t)===String(e)},this.setState=t=>{if(this.prev=this.state,this.state={...this.state,...t},!this.eq(this.state,this.prev))return this.reactive&&this.render(this.container),this.debug&&this.i===this.log.length&&this.log.push({id:this.log.length,state:this.prev,action:this.action||"setState"}),this.i=this.log.length,this.freeze(this.state),void 0!==e.emitter&&this.action&&e.emitter.emit(""+this.action,this.state),this.middleware.forEach(t=>t({...this.state})),this.action=void 0,this},this.tweenState=(t,i)=>(void 0!==e.tweenState?e.tweenState(s,t,i):this.setState(t),this),this.on=(t,i)=>(void 0!==e.emitter&&e.emitter.on(t,i),this),this.once=(t,i)=>(void 0!==e.emitter&&e.emitter.on(t,s=>{i(s),e.emitter.off(t,i)}),this),this.off=(t,i)=>(void 0!==e.emitter&&e.emitter.off(t,i),this),this.go=function(t,e){var i;return i="f"===e?this.i+t:this.i-t,this.log[i]&&this.setState(this.log[i].state),this.i=i,this},this.rw=function(t){return t?(this.go(t,"b"),this):(this.log[0]&&this.setState(this.log[0].state),this.i=0,!0)},this.ff=function(t){return t?(this.go(t,"f"),this):(this.log[this.log.length-1]&&this.setState(this.log[this.log.length-1].state),this.i=this.log.length-1,!0)},this.setCss=function(){if(!this.isNode&&this.css&&"function"==typeof this.style){var t=this.style(this.state);if(this.scopedCss){var e=this.container.id?this.container.id:this.container.className;e=e||this.uid;var i=this.container.id?"#":".",s=new RegExp(e+" ,\\s*\\.","gm"),n=new RegExp(e+" ,\\s*#","gm"),h=new RegExp(e+" ,\\s*([a-z\\.#])","gmi");t=t.replace(/}/g,"}\n").replace(/\;\s*\n/g,";").replace(/{\s*\n/g,"{ ").replace(/^\s+|\s+$/gm,"\n").replace(/(^[\.#\w][\w\-]*|\s*,[\.#\w][\w\-]*)/gm,i+e+" $1").replace(s,", "+i+e+" ").replace(n,", "+i+e+" #").replace(h,", "+i+e+" $1").replace(/\n/g,"").replace(/\s\s+/g," ")}var o=t.replace(/\n/g,"").replace(/\s\s+/g," ");this.css.innerHTML!==o&&(this.css.innerHTML=o)}},this.toString=function(){var t,e="",i=this.view(this.state);if("function"==typeof this.style&&(t=this.style(this.state).replace(/^ {4}/gm,"")),"string"==typeof i)try{e=JSON.parse(i),e=JSON.stringify(e,null,2)}catch(s){t&&(e=``),e+=(""+i).replace(/^ {4}/gm,"")}else("object"==typeof i||Array.isArray(i))&&(e=JSON.stringify(i,null,2));return e},this.render=function(e){var i="function"==typeof this.view?this.view(this.state):null;return this.isNode?this.toString():(this.container=e,requestAnimationFrame(()=>{if("string"==typeof e&&(e=document.querySelector(""+e)),this.container=e,this.css&&this.style&&this.setCss(),this.container&&i)try{t(this.container.firstChild,i)}catch(t){this.container.innerHTML=i}}),this.container)},this}}));
+!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t=t||self).Component=e()}(this,(function(){"use strict";var t=(t,e)=>{const i={cfg:{orig:t},replace(t,e=t){const i=document.createElement("template");i.innerHTML=e;const s=i.content.firstChild.nextElementSibling;s.nodeName===t.nodeName?this.iterate(t,s):t.parentElement.replaceChild(s,t)},iterate(e,i,s){(e||i)&&(this.checkAdditions(e,i,s),e&&i&&e.nodeName!==i.nodeName?this.checkNodeName(e,i):e&&i&&e.nodeName===i.nodeName&&(this.checkTextContent(e,i),3!==e.nodeType&&8!==t.nodeType&&this.checkAttributes(e,i))),e&&i&&(e.childNodes&&i.childNodes?this.cfg.lengthDifferentiator=[...t.childNodes,...i.childNodes]:this.cfg.lengthDifferentiator=null,Array.apply(null,this.cfg.lengthDifferentiator).forEach((t,s)=>{this.cfg.lengthDifferentiator&&this.iterate(e.childNodes[s],i.childNodes[s],e,i)}))},checkNodeName(t,e){const i=e.cloneNode(!0);t.parentElement.replaceChild(i,t)},checkAttributes(t,e){const i=t.attributes||[],s=Object.keys(i).map(t=>i[t]),n=e.attributes||[],h=Object.keys(n).map(t=>n[t]);s.forEach(i=>null!==e.getAttribute(i.name)?t.setAttribute(i.name,e.getAttribute(i.name)):t.removeAttribute(i.name)),h.forEach(i=>t.getAttribute(i.name)!==e.getAttribute(i.name)&&t.setAttribute(i.name,e.getAttribute(i.name)))},checkTextContent(t,e){t.nodeValue!==e.nodeValue&&(t.textContent=e.textContent)},checkAdditions(t,e,i=this.cfg.orig){if(e&&void 0===t){const t=e.cloneNode(!0);3!==i.nodeType&&8!==i.nodeType&&i.appendChild(t)}else t&&void 0===e&&t.parentElement.removeChild(t)}};Object.create(i).replace(t,e)};return function e(i){this.reactive=!0,this.debug=!1,this.scopedCss=!0;var s,n=this;return this.isNode="undefined"!=typeof process&&null!==process&&null!==process.versions&&null!==process.versions.node,this.state=i,this.view=t=>t,this.middleware=[],this.uid=Math.random().toString(36).split(".")[1],this.isNode||(this.css=document.createElement("style"),document.head.appendChild(this.css)),this.log=[{id:0,state:i,action:"init"}],this.actions=t=>(Object.keys(t).forEach(e=>{if(void 0!==this[e])return!1;this[e]=i=>(this.action=e,t[e](i))}),this),this.freeze=t=>(Object.isFrozen(t)||(Object.keys(t).forEach(e=>this.freeze(t[e])),Object.freeze(t)),t),this.eq=(t,e)=>{const i=Object.keys,s=typeof t;return t&&e&&"object"===s&&s===typeof e?i(t).length===i(e).length&&i(t).every(i=>this.eq(t[i],e[i])):String(t)===String(e)},this.setState=t=>{if(this.prev=this.state,this.state={...this.state,...t},!this.eq(this.state,this.prev))return this.reactive&&this.render(this.container),this.debug&&this.i===this.log.length&&this.log.push({id:this.log.length,state:this.prev,action:this.action||"setState"}),this.i=this.log.length,this.freeze(this.state),void 0!==e.emitter&&this.action&&e.emitter.emit(""+this.action,this.state),this.middleware.forEach(t=>t({...this.state})),this.action=void 0,this},this.tweenState=(t,i)=>(void 0!==e.tweenState?e.tweenState(n,t,i):this.setState(t),this),this.on=(t,i)=>(void 0!==e.emitter&&e.emitter.on(t,i),this),this.once=(t,i)=>(void 0!==e.emitter&&e.emitter.on(t,s=>{i(s),e.emitter.off(t,i)}),this),this.off=(t,i)=>(void 0!==e.emitter&&e.emitter.off(t,i),this),this.go=function(t,e){var i;return i="f"===e?this.i+t:this.i-t,this.log[i]&&this.setState(this.log[i].state),this.i=i,this},this.rw=function(t){return t?(this.go(t,"b"),this):(this.log[0]&&this.setState(this.log[0].state),this.i=0,!0)},this.ff=function(t){return t?(this.go(t,"f"),this):(this.log[this.log.length-1]&&this.setState(this.log[this.log.length-1].state),this.i=this.log.length-1,!0)},this.setCss=function(){if(!this.isNode&&this.css&&"function"==typeof this.style){var t=this.style(this.state);if(this.scopedCss){var e=this.container.id?this.container.id:this.container.className;e=e||this.uid;var i=this.container.id?"#":".",s=new RegExp(e+" ,\\s*\\.","gm"),n=new RegExp(e+" ,\\s*#","gm"),h=new RegExp(e+" ,\\s*([a-z\\.#])","gmi");t=t.replace(/}/g,"}\n").replace(/\;\s*\n/g,";").replace(/{\s*\n/g,"{ ").replace(/^\s+|\s+$/gm,"\n").replace(/(^[\.#\w][\w\-]*|\s*,[\.#\w][\w\-]*)/gm,i+e+" $1").replace(s,", "+i+e+" ").replace(n,", "+i+e+" #").replace(h,", "+i+e+" $1").replace(/\n/g,"").replace(/\s\s+/g," ")}var o=t.replace(/\n/g,"").replace(/\s\s+/g," ");this.css.innerHTML!==o&&(this.css.innerHTML=o)}},this.toString=function(){var t,e="",i=this.view(this.state);if("function"==typeof this.style&&(t=this.style(this.state).replace(/^ {4}/gm,"")),"string"==typeof i)try{e=JSON.parse(i),e=JSON.stringify(e,null,2)}catch(s){t&&(e=``),e+=(""+i).replace(/^ {4}/gm,"")}else("object"==typeof i||Array.isArray(i))&&(e=JSON.stringify(i,null,2));return e},this.render=function(e){var i="function"==typeof this.view?this.view(this.state):null;return this.isNode?this.toString():(this.container=e,s&&cancelAnimationFrame(s),s=requestAnimationFrame(()=>{if("string"==typeof e&&(e=document.querySelector(""+e)),this.container=e,this.css&&this.style&&this.setCss(),this.container&&i)try{t(this.container.firstChild,i)}catch(t){this.container.innerHTML=i}}),this.container)},this}}));
diff --git a/examples/usage-in-browser.html b/examples/usage-in-browser.html
index e4887a8c..c1a06f6f 100644
--- a/examples/usage-in-browser.html
+++ b/examples/usage-in-browser.html
@@ -163,5 +163,13 @@
console.log('App.log')
console.log(App.log)
+ // Test debouncing of render():
+ // - call setState 10,000 times, fast as possible
+ // - setState calls render() each time
+ // - render() should only try to access/update DOM at 60fps
+ for (var i = 0; i < 10000; i++) {
+ App.plus(1)
+ }
+