Packing as much as possible into a single cons cell, or a JS value, was (and is) important, so bit masking to distinguish between integers, floats, strings, functions, objects, was an inevitable approach.
JavaScript tried to make things artificially "simpler" by implicit conversions. As usual, the lack of consistency only lead to more eventual complexity than a sound solution would have in the first place.
Is 2 between 1.9 and 2.1? What about between 1.9 and 2.1111111111111111111111111111111111111111?
What about over/underflow? Do you wrap, clamp, throw an exception? Do you round or set +-inf? Can you divide by zero? Can you divide an integer by a floating point number, and if so what would the result be?
What happens if, say, you multiply an 8-bit and 16-bit integer and the result (using twos complement) doesn't fit in 8 bits?
"Use IEEE-754 doubles for everything" answers all these questions. I think JavaScript is basically junk, but I find at least this aspect to be rather elegant (to the point that I suspect it came from somewhere else, haha).
I'm not so sure about that. There is an alternative implementation of Lua called LuaJIT, which uses a similar NaN-tagging trick, and it also happens to be incredibly fast. LuaJIT uses the 5.1 version of the language, with some backported 5.2 features for compatibility, but it will likely never backport 64-bit integers from 5.3.
It's resulted in something like the Python 2/3 split. Except it's even worse because Lua has relatively little penetration as a general purpose scripting language, but is quite popular as a language that can be embedded in a program to add scripting capabilities. This means it's up to the developer of said program which version of Lua they choose to embed, and from what I can tell, most developers choose speed over 64-bit integer support.