Speaking from experience from my long standing (and slow-moving) work on a Ruby ahead-of-time compiler [1], as Ruby and JS has most of the same issues:

The first and most obvious problem in terms of performance is that for sufficiently dynamic languages it is exceedingly hard to know what types will look like at runtime. Both in JS and Ruby you can define types at runtime. In Ruby by mutating classes at runtime, and in JS by mutating prototypes.

This means that it's much harder to e.g. lay out objects so that you can cheaply access attributes just through indexed reads for example. It also makes e.g. inlining very hard, as it may be hard to impossible to even determine the types of objects you'll get passed. There are lots of workarounds, but many of them bring you progressively closer to including a full JIT anyway.

For a JIT like V8, you can infer/guess at types because you have the entire infrastructure to correct your guess. E.g. if you come across a "Point" class that appears to have the attributes/instance variables "x" and "y", you can allocate those objects, and if a "z" is introduced later, you can either create a hidden subclass dynamically or you can re-allocate all existing Point instances with an extra field, and re-compile the code that accesses it accordingly. In an ahead of time compiler, this is harder without either embedding a JIT anyway, or embedding specialized code that carries out specialised code generation tasks etc., or trying to infer a single unified type (but this is problematic; code that is validly executed for the Point class when the instances doesn't have "z" could very well fail or behave entirely differently once it is mutated to add it)

Ultimately I believe that a lot of dynamic code is ultimately de-facto static, but even for code that in effect is 100% static at runtime, you sometimes may need extra hints to guarantee that. E.g. any code that loads a library could potentially be intended to be static, but for dynamic languages you may need extra hints beyond the semantics of the language to know whether the library is meant to always be static, or e.g. is intended to be a plugin mechanism. (A typical Ruby idiom is to iterate over a directory listing and "require" every .rb file found in it; that maybe to avoid having to explicitly list all of them, or it may be to let people drop in plugins)

Once you know where that delineation is, there's often nothing particular preventing type inference across the board, but it can be a lot* of effort. E.g. in both Javascript and Ruby, you face the situation that an analysis of the types that is valid in one instance can be invalid the next, so must be at least in theory prepared to run type-inference several times and potentially be forced to generate different code based on when in the program fow a method is called.

E.g. even in my compiler implementation, I'm likely to, for the ease of bootstrapping, end up replacing methods with more capable versions during the bootstrap of the runtime library, and it may very well end up altering which types are safe inputs to a given methods.

Dynamic languages also often make it tricky to determine conclusively what should be compile errors. E.g. a lot of constructs in Ruby will trigger exceptions that seems like they should be compile time errors. For example there's a LoadError if "require" fails to find a file. But most of them can also be caught, and then for an AOT compiler you face the challenge of deciding whether to try to process that at compile time (e.g. conditionally including an optional dependency can be done this way) or generate code to try the load at runtime (and thus losing a lot of the ability to statically infer the precise types at compile time, as for what you know the runtime version of this file may redefine everything).

For my part I intend to resolve a lot of this with pragma's and compiler flags to let you signal whether to favour precise compliance with the interpreted Ruby, or making it as static as possible (if I ever get that far...)

[1] http://hokstad.com/compiler