javascript optimizations are always super weird to me and i end up profiling bizarre things
today, i wanted to be able to provide default implementations to either plain javascript objects or ES6 class instances, so i had something like this
class DefaultImpl {
constructor(methods) {
if(!isPlainObject(methods)) {
return methods
}
this.methods = methods
}
aMethod() {
return this.methods.aMethod?.() ?? someDefaultImpl()
}
}
and my first thought was, “i wonder if i can overwrite aMethod when its called with either the default or given implementations to avoid the null coalescing check?
// ...
aMethod() {
if(this.methods.aMethod != null) {
this.aMethod = this.methods.aMethod
return this.methods.aMethod()
} else {
this.aMethod = someDefaultImpl
return someDefaultImpl()
}
}
}
the performance results for both approaches were identical on v8, spidermonkey, and JSC. i’m assuming this is because the shape of a prototype is highly optimized and the one that overwrites the function violates the browsers expectations and suffers a performance penalty while the other one doesn’t? and if that’s the case, maybe the null coalesce vs the prototype change ends up being a wash. or maybe branch prediction on the null coalesce makes the performance hit basically nonexistent? i’m not sure yet
maybe the weird thing about this is that i thought that “oh yeah i can just overwrite the method at runtime” was a good idea
@rowan I think this might be tracing JIT effectively doing the same thing at runtime.