-
-
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