There's plenty not to like about JavaScript, but the biggest mess is this:

js> x = 1
1
js> z = x.y
js> // WTF??

In other words: it's not an error to access an attribute of an object which isn't there. It's dicey enough that you can have attributes in Python and Ruby that can be misspelled, and mismatch their declarations, without an immediate compiler error. But, that is reasonable to deal with as long as you have pretty good unit test coverage: after all, if you run the code that is actually accessing the attribute, you'll quickly see that there's a run-time error and fix it:

irb(main):001:0> x = 1
=> 1
irb(main):002:0> z = x.y
NoMethodError: undefined method `y' for 1:Fixnum
from (irb):2

~or~

>>> x = 1
>>> z = x.y
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'int' object has no attribute 'y'

... and you need unit test coverage anyway, because the compiler can't save you from a huge variety of other violations, so it's not like this is really making much extra work for you.

In JavaScript, by the time you actually encounter an error, it's too late to figure out where the heck the erroneous object is getting generated. So, if you have some code like 'this.observers.push(object.somefunc);', every test case which adds an observer must also verify what happens when the observer gets called: and it has to be the same test so you have some idea where the observer came from, whereas you can easily make those things different tests in Python.

Then, in order to get reasonable error-reporting behavior from quick things which aren't tested, you have to have tons of manual type-checks anywhere that objects are put into a persistent container, because by the time you have some random 'undefined' in your list of observers, it's far too late to figure out how it got there.

This type of paranoid defensive programming is a bad idea in Python and Ruby, because you can just let the language runtime do its job and inform you if there's an error, and your stacktraces will give you a good idea where it is.

The fact that sometimes unknown things are 'undefined' and sometimes they're 'null' and sometimes they're '"undefined"' and sometimes they're the empty string and sometimes they're 0 really compounds this problem. Python has None, Ruby has nil, and nobody uses random ad-hoc sentinel values because why would you do that?

The thing that just blows my mind is that the designers of JavaScript must have _known_ that this was a terrible idea, because:

js> undefined.undefined
typein:1: TypeError: undefined has no properties
js> null.nothing
typein:2: TypeError: null has no properties

so when you are two steps out from the misspelling that caused your error, you can figure out that something has in fact gone wrong. And:

js> blub
typein:3: ReferenceError: blub is not defined

since the assumption that everything you really care about in JavaScript is going to be a global variable, and prototype attributes are kind of an afterthought. After all, why would you store data in organized structures when you can just stuff it all into an undifferentiated bag of crap in the global namespace! It's all going to go away when you reload the page, right?

Except then Node comes along and changes the equation so you actually have to live with your persistent data structures and you probably want to know when things go wrong in your long-running servers. Oops.

Here I really have to agree with Ted: despite the fact that there are advantages to be had from keeping your client and your server in the same language and leveraging your investment in utility libraries in both places, JavaScript really is bad enough language that it's worth the effort to use something different when you can.