-
-
Save WebReflection/5aaca727bc3b784d43be3704cf65abff to your computer and use it in GitHub Desktop.
| // Players | |
| class ClickCounter { | |
| constructor() { this.clicks = 0; } | |
| onclick(e) { this.clicks += (e.type === 'click') ? 1 : -1; } | |
| } | |
| class Handler extends ClickCounter { | |
| constructor(currentTarget) { | |
| super(); | |
| currentTarget.addEventListener('click', this); | |
| } | |
| } | |
| class DynamicHandler extends Handler { | |
| handleEvent(e) { this['on' + e.type](e); } | |
| } | |
| class StaticHandler extends Handler { | |
| handleEvent(e) { switch (e.type) { | |
| case 'click': return this.onclick(e); | |
| }} | |
| } | |
| // just to rule out hierarchy performance | |
| class Bounder extends ClickCounter { | |
| constructor(currentTarget) { super(); } | |
| } | |
| class ArrowHandler extends Bounder { | |
| constructor(currentTarget) { | |
| super(currentTarget); | |
| this.click = (e) => this.onclick(e); | |
| currentTarget.addEventListener('click', this.click); | |
| } | |
| } | |
| class BoundHandler extends Bounder { | |
| constructor(currentTarget) { | |
| super(currentTarget); | |
| this.onclick = this.onclick.bind(this); | |
| currentTarget.addEventListener('click', this.onclick); | |
| } | |
| } | |
| // Rules | |
| const benchmark = (Class, length = 1000, samples = 5) => { | |
| const currentTarget = button(Class.name); | |
| const instances = new Array(length); | |
| return new Promise(res => setTimeout(res, 500)).then(() => { | |
| let benchName; | |
| let memory; | |
| benchName = `new ${Class.name}(currentTarget)`; | |
| memory = performance.memory.usedJSHeapSize; | |
| console.time(benchName); | |
| for (let i = 0; i < length; i++) | |
| instances[i] = new Class(currentTarget); | |
| console.timeEnd(benchName); | |
| memory = performance.memory.usedJSHeapSize - memory; | |
| if (memory) console.log('memory: ', memory); | |
| const event = new Event('click'); | |
| benchName = 'currentTarget.dispatchEvent(clickEvent)'; | |
| memory = performance.memory.usedJSHeapSize; | |
| console.time(benchName); | |
| for (let i = 0; i < samples; i++) | |
| currentTarget.dispatchEvent(event); | |
| console.timeEnd(benchName); | |
| memory = performance.memory.usedJSHeapSize - memory; | |
| if (memory) console.log('memory: ', memory); | |
| console.assert( | |
| instances.every(instance => instance.clicks === samples), | |
| `expected ${length} clicks, got ${instances[0].clicks} instead` | |
| ); | |
| }); | |
| }; | |
| // Helpers | |
| const button = textContent => { | |
| const el = document.createElement('button'); | |
| el.textContent = textContent; | |
| return document.body.appendChild(el); | |
| }; | |
| // Race ! | |
| var instances = 10000; // how many instances ? | |
| var dispatches = 10; // how many dispatches ? | |
| Promise | |
| .resolve() | |
| .then(() => benchmark(DynamicHandler, instances, dispatches)) | |
| .then(() => benchmark(StaticHandler, instances, dispatches)) | |
| .then(() => benchmark(ArrowHandler, instances, dispatches)) | |
| .then(() => benchmark(BoundHandler, instances, dispatches)) | |
| ; |
I guess instead of "expected ${length} clicks, got ${instances[0].clicks} instead" we should use "expected ${samples} clicks, got ${instances[0].clicks} instead", aren't we?
Just one more thing.
I launched a code of this benchmark (Chrome Version 64.0.3282.186 (Official Build) (64-bit), MacOS High Sierra) and got some different results, which shows an enormous memory consumption for handleEvent.
@WebReflection, can you, please, comment on that?

I've posted a question on StackOverflow.
The answer is pretty simple.
handleEvent consumed that much memory due to issue in the benchmark.
The thing is that strings concatenation ('on' + e.type) causes new string allocation in heap on each iteration.
If we just replace this['on' + e.type] to switch(e.type) {case 'click': this.onclick(e);}, then we get results, which are reflecting initial idea regarding benefits of using handleEvent.

Alternative with perf-monitor