Lua is smaller, more consistent, and much more flexible with integration since it just C.
Debugging is built into the language in a simple way.
_G gets a table of all global variables
debug.getlocal allows you get any and all variables at each stack frame at that point including name, value and type.
You can even use debug.sethook to have a callback after every function, line, or instruction.
On top of all that you have LuaJIT which not only runs ridiculously fast, but can deal with dynamically loaded files incredibly fast. It even has a direct foreign function interface and allows you to use shared libraries directly without writing any more C or recompiling them.
Your last paragraph is the essence of why I chose Lua for my own projects, especially the FFI, but unfortunately doesn't apply to all of Lua. The divergence between Lua and LuaJIT is certainly not a strength.
The 'stock' Lua interpreter is very fast, very small (good for cache retention) and all its C functions are reentrant, so no global interpreter lock: those apply to both flavors.
As the tiniest of nitpicks, `_G` is the default home of the global environment, but it's just a variable. The correct way to get the environment is `getfenv(1)` or just `_ENV`, depending on your flavor.
> Debugging is built into the language in a simple way.
Lua debugging interface notably lacks breakpoints. You can simulate breakpoints by per-line hooks but it's not performant. Many popular JS engines including V8 do support performant breakpoints by bytecode patching, and Lua do have multiple patches doing the same (well, a common theme) but none is official.
Tables are immensely better than JS objects, in every way. I could entertain a separate array type in the abstract, but given what actually exists, I will take tables over the JS collection of objects, arrays, and maps, any day.
Extensible syntax, which is used to implement "objects", but also to, well, extend the syntax. Indexing, assignment, calling a table like a function, operator overloading: it all works the same way, and it all works. Metatables are superior to prototype chains, simple as.
Coroutines. I don't have the energy to write up my personal take on why these are the best baseline abstraction for cooperative concurrency in a dynamic language; one of these days I'll publish something so I can simply link to it.
Multiple return values. A language without variadic parameters and multiple returns is less dynamic than one which has them, if you're going to go dynamic, go all in.
Environments! This is one of those things which you might not need very often, but when you do: oh man, great to have. Number one thing I would carry over into the browser, where we really don't want to leak context, and JS fights against this constantly.
As minor desiderata, Lua's type coercion, the fact that 0 is truthy, a separate `..` concatenation operator, and optional semicolons which basically cannot bite you: Lua has no need for a separate `==` and `===`, and I consider that a strength. I also think the separate `null` and `undefined` is clunky, but that opinion isn't strongly held; Lua has considered adding an `undef`, and it's an interesting debate which I'm just barely on the "no" side of.
Debugging is built into the language in a simple way.
_G gets a table of all global variables
debug.getlocal allows you get any and all variables at each stack frame at that point including name, value and type.
You can even use debug.sethook to have a callback after every function, line, or instruction.
On top of all that you have LuaJIT which not only runs ridiculously fast, but can deal with dynamically loaded files incredibly fast. It even has a direct foreign function interface and allows you to use shared libraries directly without writing any more C or recompiling them.