There are two separate issues at play here. One is NaN--you can either have NaN values or have errors from invalid mathematical operations. Since JavaScript only has floats (which contain NaN values), NaNs make just as much sense as errors.
Another, more general, issue is type coercion. You can either have a "strongly typed" or "weakly typed" paradigm (although both terms are somewhat nebulous). I rather like JavaScript's coercing everything--after all, a 1 is a 1 even if it happens to be a string. Thus, I think concatenating a string with a number or trying to do arithmetic with strings makes perfect sense.
A different option--this is what Python does--is to not coerce types. So trying to concatenate a string with a number would give you a type error. However, this also has an advantage: you can now reasonably overload your operators. You can have + add numbers, concatenate strings, append lists...etc.
The real problem comes from trying to have both type coercion and an overloaded + operator; either option by itself is reasonable. JavaScript--probably in an unfortunate attempt to ape Java--has an overloaded + operator that does both concatenation and addition. If it instead had just a separate concatenation operator, the endless problems with + would not arise.
You're quite right in that this is a design choice rather than a defect. I do, however, have a preference for strongly typed languages (and there are very different flavors thereof, from Python to C++ to SML). I think weak typing is great for getting prototypes out the door in a hurry (or even just for getting up to speed in a language in a hurry), but dynamic typing really helps in the later stages of an application.
First, let's be clear on terminology: dynamic is the opposite of static; weak is the opposite of strong; the two are orthogonal. "Weak" and "strong" typing is about coercion; static and dynamic is about compile-time verification.
I personally think that if your language is dynamically typed, it should be weakly typed; if your language is statically typed, it should also be strongly typed. So I would (and do) prefer JavaScript/Haskell to Python.
I figure if you're going to be dynamically typed, you may as well be as flexible as possible; it also makes sense from a semantic point of view (my whole a 1 is a 1 is a "1" spiel). On the other hand, if you're enforcing types at compile time, having type coercion just seems weird. (Of course, you can have ad hoc polymorphism like Haskell's type classes that could do something similar, but that's different.)
I think the practical difference between "weak" and "strong" typing is much smaller than the difference between dynamic and static typing.
Finally, I do think the behavior of + is a defect, but not because of coercion. Rather, I don't like how it does two different things. If it only added numbers, it would work perfectly; the other arithmetic operators all work as expected. (Yes, I think the NaN behavior makes sense.)
Another, more general, issue is type coercion. You can either have a "strongly typed" or "weakly typed" paradigm (although both terms are somewhat nebulous). I rather like JavaScript's coercing everything--after all, a 1 is a 1 even if it happens to be a string. Thus, I think concatenating a string with a number or trying to do arithmetic with strings makes perfect sense.
A different option--this is what Python does--is to not coerce types. So trying to concatenate a string with a number would give you a type error. However, this also has an advantage: you can now reasonably overload your operators. You can have + add numbers, concatenate strings, append lists...etc.
The real problem comes from trying to have both type coercion and an overloaded + operator; either option by itself is reasonable. JavaScript--probably in an unfortunate attempt to ape Java--has an overloaded + operator that does both concatenation and addition. If it instead had just a separate concatenation operator, the endless problems with + would not arise.