“Why Does JavaScript Suck?”

August 07, 2014 (February 29, 2016)

I am not planning to continue writing exclusively about programming languages, but since I seemed to get away fairly well with my last rant about why PHP sucks, let’s continue down the same road and bash just a little on JavaScript. Repeatedly. With a sledgehammer. Smile While JavaScript’s quirks aren’t nearly as bad as PHP’s, they are still annoyances and will make your life miserable. It’s another example of a language that’s here to stay, simply because it is the only one that has been widely accepted and adopted for client-side browser scripting. At least with PHP you have a choice, with JS you don’t. You’re stuck with it. And guess what, it sucks.

Alright then, why does this one suck?

Even though at first glance JavaScript seems like a good solution for browser scripting, it does have significant drawbacks and shortcomings that put it into a bad light. Unlike languages that are badly designed from the start and a nightmare to work with, JavaScript just has some quirks that may or may not cause serious head injuries. Then again, I don’t expect too much from a language that supposedly has been designed within ten days. I don’t hold too big of a grunt against JS though, which is why I want this article to be less of a rant and provide more constructive information. However — don’t get me wrong — even though it’s possible for me to embrace its flaws and work with the language, it is not a pleasant experience. Frankly, it sucks. Let’s begin and explore why.

Language Presentation

JavaScript is a dynamically typed, prototype-based programming language that honors the ECMAScript Standard, thus supporting imperative, object-oriented and functional programming paradigms. I’m not a huge fan of languages without a strict type system, but I do recognize both their uses and their strengths for certain applications — if they didn’t screw this one up. Ambivalent

The Java in JavaScript

One might assume that JavaScript is some weird Java spin-off specifically designed to be run in a browser environment. Well, the names are certainly similar but that’s about where the commonalities stop. These are two entirely different languages that share a few keywords, which most likely originated from their common ancestor, C.

JavaScript was undergoing several name changes, first being developed under the name Mocha, later renamed to LiveScript and finally christened JavaScript. It’s a pretty dumb and misleading name that caused some confusion, but back then this was an ingenious marketing ploy that probably helped JavaScript quite a bit to rapidly gain popularity.

Type System

According to the ECMAScript Specifications, the language comes with exactly six different built-in types: Undefined, Null, Boolean, String, Number, and Object. Practically it’s only four, because Undefined and Null only ever represent their own value, undefined and null respectively.

IEEE 754: A Poor Choice

What really bothers me is that they decided to go with the IEEE 754 Double Precision standard, meaning that all you get is this one Number type that only supports 64bit doubles. There are no native integers in JS. The problem with representing integers with floating point variables is that they are losing precision once the numbers get too large. For example, 9999999999999999 == 10000000000000000 evaluates to true. This is entirely correct in terms of the IEEE 754 standard (and so is typeof NaN == 'number'), though it might lead to errors if programmers are not aware of this fact. There is no type promotion or similar mechanisms that avoid this problem automatically.

The Quirks

JavaScript

JavaScript does not always behave how you’d expect. My desk at work had to endure being hit time after time while I was in agony, trying to debug JS code that was interacting with the company-internal SAP system (oh gosh, a story of its own for another time). I noticed that most of the bugs were due to tiny mistakes that were being carried on and on until the application crashed. I followed that it must be quite hard to detect errors early when they happen. I favor a fast-fail behavior (say via throwing an exception right when something unexpected happened). Since JavaScript is dynamic and rather flexible, it just loves to ignore these things and continue on, trying to make sense of what state is remaining. At this point I’m just thankful that I don’t have to spend most of my time at work programming in JS.

Aggressive Type Coercion

JavaScript practices an extremely aggressive type coercion doctrine where comparing apples with bananas always makes sense, especially when they are actually oranges. Everything works somehow. Or not, depending on how you look at it and what time of the day it is. The language also seems to have a deep sensual relationship with strings, trying to convert in and out of them. If you look at the lengthy definition of the Abstract Equality Comparison Algorithm, you might realize why sometimes the thing doesn’t just quite do what you want:

11.9.3 The Abstract Equality Comparison Algorithm
The comparison x == y, where x and y are values, produces true or false. Such a comparison is performed as follows:

  1. If Type(x) is the same as Type(y), then
    1. If Type(x) is Undefined, return true.
    2. If Type(x) is Null, return true.
    3. If Type(x) is Number, then
      1. If x is NaN, return false.
      2. If y is NaN, return false.
      3. If x is the same Number value as y, return true.
      4. If x is +0 and y is −0, return true.
      5. If x is −0 and y is +0, return true.
      6. Return false.
    4. If Type(x) is String, then return true if x and y are exactly the same sequence of characters (same length and same characters in corresponding positions). Otherwise, return false.
    5. If Type(x) is Boolean, return true if x and y are both true or both false. Otherwise, return false.
    6. Return true if x and y refer to the same object. Otherwise, return false.
  2. If x is null and y is undefined, return true.
  3. If x is undefined and y is null, return true.
  4. If Type(x) is Number and Type(y) is String, return the result of the comparison x == ToNumber(y).
  5. If Type(x) is String and Type(y) is Number, return the result of the comparison ToNumber(x) == y.
  6. If Type(x) is Boolean, return the result of the comparison ToNumber(x) == y.
  7. If Type(y) is Boolean, return the result of the comparison x == ToNumber(y).
  8. If Type(x) is either String or Number and Type(y) is Object, return the result of the comparison x == ToPrimitive(y).
  9. If Type(x) is Object and Type(y) is either String or Number, return the result of the comparison ToPrimitive(x) == y.
  10. Return false.

Accept my apologies for this wall of text, but I do think that it helps a lot to read through these definitions at least once and draw a few conclusions:

1
2
3
4
5
6
7
8
9
true == 1               // true
true == "1" // true
false == 0 // true
false == "0" // true

false == undefined // false
false == null // false

null == undefined // true

That seems alright, doesn’t it? Well, the fun starts when the operands get a little more complicated. Whitespace is completely irrelevant for the comparison if it involves both strings and numbers.

1
2
3
"\t\r\n" == 0                   // true
"\t\r\n 16 \t\r\n" == 16 // true
"\t\r\n 16 \t\r\n" == "16" // false

At least with JS you don’t have to worry about implicit conversion if the string contains a hexadecimal or octal number and the comparison won’t be just aborted if the operand is not a correctly formatted number (yes, I’m looking at you, PHP Ambivalent). Except for the obvious exception — of course — the parseInt(string, radix) function that has the exact same backwards behavior if radix is not specified.

So, what happens when we bring arrays into play? Well, [] == [] (array == array) evaluates to false, of course. Alright. Maybe it’s considering them as individual logical objects. That’s okay. Though what happens if we try [] == ![] (array == not array)? It evaluates to true. What? Confused

If you start comparing non-empty arrays, the strange behavior doesn’t stop:

1
2
3
16 == [16]         // true
16 == [1,6] // false
"1,6" == [1,6] // true

What is happening here is that JavaScript first converts the array into a string and then into a number. The second line evaluates to false because [1,6] is actually translated to "1,6", which is what the third line shows.

Furthermore, beware of string concatenation involving numbers and make sure the result is what you intended:

1
2
3
var a = "1"
var b = 2
var c = a + b // c = "12"

The variable b will be coerced into a string and then appended to a, resulting in the final string “12”. To prevent this from happening (or rather to enforce the behavior you want), you may explicitly annotate the type:

1
2
3
var a = "1"
var b = 2
var c = +a + b // c = 3, note the unary plus sign (+a).

You are able to enforce numeric (floating point) comparison with +a == +b, numeric integer comparison with a|0 == b|0, string comparison with "" + a == "" + b and boolean comparison with !a == !b. It’s pretty dumb, but hey, at least it works. Undecided

I strongly recommend the use of the Strict Equals Operator === and its counterpart !== whenever possible. The additionally involved type check can and will help avoiding nasty mistakes and bugs that may be very hard to detect.

An Awesome Side Effect

If you take this behavior to a whole new level you can achieve things beyond good and evil. The following little snippet just blew my mind to smithereens. Fire up your JS console in the browser (alternatively the address bar might work), and run this one-liner:

1
alert((![]+[])[+[]]+(![]+[])[+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]);

I’m not even mad — that’s fantastic! Laugh Check out the JSF*ck website that is dedicated to generating these instructions.

Global Variables

Just like many other programming languages, JavaScript supports global variables. The problem here is that JS heavily depends on them, making them really annoying to deal with. You can’t just avoid them, you actually have to work with them. There is no way around. Since they are accessible within every scope of your program, you never know whether they have been changed or not, possibly even by third party libraries that happened to use the same variable names. Global variables may be useful for smaller scripts, but usually they seriously increase the complexity of your programs. In browser environments, all global variables are added to the global object window.

To add to the misery, undeclared variables will be promoted implicitly to global variables. This may easily happen if you accidentally misspell your variable and thus create a global variable you didn’t intend to create.

1
2
3
4
5
6
7
function foobar() {
var bar = 10;
//
// [...Code...]
//
baz = 20; // Typo. "baz" is now a global variable.
}

I noticed that a few of my co-workers simply forgot to declare their variables, which seems to be an extremely common mistake in for loops. I remember one occasion involving a function that was supposed to calculate the total expenses for all products delivered by the top ten suppliers. Greatly simplified, the function looked something like this.

1
2
3
4
5
6
7
8
9
var calculateTopTenCosts = function() {
var sum = 0;

for(top = 0; top < 10; top++) {
sum += getRankCosts(top);
}

return sum;
}

Luckily, this bug was pretty easy to find since the function always returned 0. So what’s wrong with it? The counter variable top has not been declared properly using the var keyword. Due to JavaScript’s implied globals, JS creates the variable in global scope. Now in this case it can’t actually do that because top is already a global variable — window.top — that returns the top most window object. The for loop will simply be skipped since top < 10 will always evaluate to false due to the fact that top is an object and not a number.

If the variable was named something differently, it might have worked correctly until someone introduces a new global variable, breaking the existing code without any changes to the actual algorithms…

Semicolon Insertion

I personally prefer having control over the structure of my statements. Other programmers however wholeheartedly despise semicola and regard them as unnecessary typing overhead. Both are valid opinions and whether you have to write them or not is determined by the language. In JavaScript, this is a little weird, Although you can use semicolons, you don’t have to. The parser tries to figure out by itself where end-of-statement markers are required and where not. Unfortunately, this doesn’t always work out:

1
2
3
4
5
6
function foo() {
return // Point of Insertion.
{
bar : "test"
};
}

The purpose of this function is to return an object that has a property bar with the value "test". Sadly, coding the function this way will yield the wrong result because the parser erroneously inserts a semicolon at the wrong place, preventing the function from returning the object. Just one of many things to keep in mind during development.

Lack of Proper Scoping

In contrast to most other programming languages that use C-style syntax, JavaScript lacks proper scoping support for blocks. Functions are the only means to achieve scoping. Variables declared anywhere within a function will be visible to all blocks of that function. I recommend declaring all local variables at the very top of the function in order to work around possible mistakes due to scoping.

1
2
3
4
5
6
function foobar(a) {
if(a === 0) {
var c = 20;
}
return c; // c is still visible.
}

It really sucks that there is no good way to structure larger applications. Of course you can (and should!) use objects and functions to encapsulate functionality and state. It’s also possible to use these concepts as a substitute for namespaces, though it is rather annoying and verbose to program that way.

Miscellaneous Annoyances

Here are a few additional things to look out for during development that I didn’t want to put in the other sections of this article.

Unexpected Behavior

Some built-in functions or those provided by third-party libraries may yield unexpected results. For example, the default behavior of Array.sort() is to order alphabetically, even if an array only contains numbers:

1
[5, 12, 9, 2, 18, 1, 25].sort(); // [1, 12, 18, 2, 25, 5, 9]

To get the order right, you’ll have to do something like this:

1
2
3
[5, 12, 9, 2, 18, 1, 25].sort(function(a, b){
return a - b;
});

Of course this is what manuals are for; though it doesn’t hurt to mention it here.

Literals / Objects

Remember that literals (numbers, strings, boolean values, null, and undefined) are not actually instances of their corresponding ‘wrappers’:

1
2
3
var a = "foobar"
var b = typeof a // 'string'
var c = a instanceof String // false

Only objects that are created using constructors (e.g. new String("foobar")) will yield the expected results. Anyway, those wrappers are not particularly useful and probably confusing and unnecessary at best.

Numeric Boundaries

Another trap you need to watch out for is this one:

1
-1 < Number.MIN_VALUE // true

What the hell?! Well this one is really silly. The true minimum value is actually -Number.MAX_VALUE. Confused -Number.MIN_VALUE holds the smallest positive number that can be represented by the system. It’s just an unfortunate choice of name.

JSLint

If you’re programming JavaScript by hand (i.e. not using a compiler to compile into JS), I suggest you take a look at JSLint. It’s a nice tool that comes in very handy during development. It allows you to detect possible mistakes early and helps you to write better, cleaner JS code. The tool follows a set of coding conventions you might want to follow too.

The Dream

JavaScript is undeniably going to stay around for a very, very long time. It is one of the most popular and most wide-spread programming languages in existence, making it nigh impossible to replace it. It sucks because of all its flaws, it sucks that it has become a language it was never intended to be.

The landscape of general programming and web development has never been as diverse before as it is now. As programmers, we need choices and we do have choices to make in terms of tools and platforms. Environments like the Java Runtime and the .NET Framework allow us to use different languages for different tasks while still maintaining compatibility to the others.

Browsers only speak JavaScript, so we are forced to use this terrible language. Or are we? Maybe we’re just looking at it from the wrong perspective. Maybe we shouldn’t think of JavaScript as a programming language but rather as an environment or a “build target”. JS is quite a simple language at its roots, meaning it can be effectively optimized by JIT compilers.

There are numerous tools and compilers available, developed and improved by a vibrant community of web developers.

I believe that JavaScript is going to be pushed back in the near future. Maybe even so far back that it won’t directly concern us anymore. JavaScript will be running under the hood, comparable to the JVM, making the web truly language independent.

Keep a close eye on asm.js, it might just save us all! Wink

Conclusions

JavaScript might be a horrible language to work with, but it does have great potential as an environment. Douglas Crockford made a short yet accurate statement in his book JavaScript: The Good Parts (which I highly recommend, it’s an excellent read):

JavaScript is built on some very good ideas and a few very bad ones.

That sums it up pretty well. JS is what it is. Given the right circumstances and a few more years of development, I think the good parts might start to shine through as other tools start to gain popularity and acceptance.

Outstanding libraries and frameworks like jQuery make JavaScript a lot more bearable (really, who doesn’t use jQuery nowadays?). It is time that we start embracing the language as a powerful platform that basically runs on any device. Wearables, phones, tablets, laptops, desktops, even clusters of servers — JavaScript will be available.

It’s a terrible language with numerous flaws, though it does have a bright future.

…and exactly that's why programming in JavaScript sucks.