Here's the problems:
1) Often, you have lots of code that only runs once (e.g. UI initialization), or runs rarely (click handlers). By the time the JIT has had an opportunity to observe this code and optimize it, it may be too late.
2) The dynamic nature of JS means that making your code JIT-friendly requires deep knowledge of what language features to avoid.
3) Performance isn't only driven by having the tightest possible machine code. Memory layout is often more important on modern processors. Indirection causes cache misses, and JS objects contain far more indirection than equivalent structs defined in C/C++/Rust. Further, in Javascript you can't really create cache-friendly collections of objects like vectors, BTreeMaps, etc.
1) Gathering information at runtime competes with the actual program for resources.
2) Optimizing at runtime competes with the actual program for resources.
3) Most importantly, JITed languages tend to be pointer-heavy and full of indirections, and there's not much a JIT compiler can do about that.
But in practice they take a long time to warm up, may or may not get faster, and in general don’t attempt total program optimisation. As some parts of the node standard lib is native code and can’t be optimised at runtime.