JavaScript: its history, and some of its quirks
This article was inspired by many talks from Douglas Crockford and by the creator of JavaScript, Brendan Eich (links below).
During the distant year of 1995, Netscape, the company making the dominant browser in that time, looking at how the Web was evolving, felt the urgent need to implement a client language on its browser, to give some dynamic properties to the Web, that by then survived only with static websites.
They were very impressed by the marketing success of the Java language by then and, as a company willing to survive once Microsoft had just entered its segment, it pursued a collaboration with Sun to port Java into its browser. However, some time after, they sensed that, as good as it would be for marketing, Java would be ill-suited for the task, as the requirements would ask for something much leaner. So, in parallel, through the hands of an experienced guy named Brendan Eich, Netscape came up with the task of porting Scheme (basically a slim subset of the legendary language Lisp) to be the scripting language of the Netscape Navigator, in a move that would have shaped our future in a totally different way.
But at the same time that Java was deemed too cumbersome, Scheme was deemed too “weird” to be the chosen language, at least for most tech people of the time, mostly used to Java and C, so they ultimately decided to try creating a new language from scratch. For that, though, Brendan had been given just ten days to come up with a language suited for the needs of the Web (by then). Should he didn’t come up with something usable by that time, Netscape would just turn to Java and implement it somehow on browsers, so he felt he had to rush. Eich himself blames some of the JS design mistakes to the fact that he couldn’t get enough sleep on those ten days.
Armed with good intentions and cleverness, Eich put together in this ridiculous timeframe a language all Frontend programmers learned to love, or hate, or both. As said, Java was very popular then, so the initial name Netscape had for the language (LiveScript) was replaced for something more “marketing-savvy”, for this new language to be perceived as a subset of Java, just to attract developers away from whatever Microsoft was planning to do. That way, JavaScript was born. Aside from the C-like syntax, JS had little in common with Java: among the many differences, it was not statically typed and not based on classes but rather on prototypes.
From the very beginning JS attracted attention, but first it was treated more as a gadget than something truly useful by most developers, being majorly complimentary to wrap-up page functionalities in optional, small and shallow scripts. Meanwhile, Microsoft tried to have its own language on browsers, but nobody wanted what was called VBScript, the “portable” version of MS’s Visual Basic, so much that it felt compelled to reverse-engineer the JavaScript from Netscape to implement it in IE, dubbed simply as “JScript” (because Java is a trademark from Sun). This implementations of JS, however, had significant differences from Netscape’s original one, so developers frequently had to write completely different codes for the different browsers, discouraging them to get deeper on JS.
This initial Microsoft v.s Netscape divide (called “browser wars” back then) shaped many of JS inconsistencies. For the tricking vs. bubbling item you’re going to read about below, for example, Netscape wanted one and Microsoft wanted another and both ended up being implemented. During most of the initial ten years of JS, it was a language shaped inconsistently because of different company directions and lack of cooperation. This has become less and less of a problem when the language was standardised together with the European standards group ECMA, but until we got there developers had to use a lot of strategies to deal with syntax disagreements (and there are some until today, although minor).
A non-exhaustive list of JavaScript quirks
It is good news that there are many items I could have put here that were deprecated, at least in practice, because of the newer ECMAScript standards (with
, scopes, new
), but the list below is what I judge to be the one that’s most helpful considering how JS is used nowadays.
Prototypical inheritance
I feel this is a feature that never got the love it deserved, partly because it was somewhat buggy in the first years of JS (and JS use was shallow back then, as I said). Instead of all the cognitive baggage of classes, we have a simple tree relationship between objects. Eventually, due to demand of people too used to “vanilla” classes, we got it in JS but it was just “syntatic sugar” and used the same prototypical inheritance internally, which by itself shows that these prototypes are not that much less powerful than standard native classes.
Arguably, had we not been trained and wired to deal with classes and their complexity in all of our courses and colleges, prototypes would be more clearly seen as a much simpler solution to achieve more sustainable object orientation. In JS, almost everything is an object, one object inherits form another or it doesn’t inherit from anything, and that’s it: as easy as it can get on object orientation.
This is the pretty much only “positive quirk” of JS on this list, which doesn’t mean it is a bad language or even that I hate the language or anything like this. But it surely means it is a quirky one.
Deceptive numbers
The number system of JavaScript was one of the victims of the lack of time when the language was developed, but its 64-bit binary format is mostly adequate for a general use. Still, some very weird situations come about when we do simple floating point operations like 0.1 + 0.2
. This sum will not result in 0.3
but, rather in 0.30000000000000004
(there are more digits after the “4”, but on JS console it will show as this), so there is a possible problem to do equality checks that will require the developer to take a special attention on this case, which is tricky because in many real-life situations people will not even be aware that this happens.
This is a consequence of the floating point binary format that JS features, which leads to approximations when converting to and from our decimal number system, approximations that are fundamentally imprecise. To understand it better, I suggest an in-depth article like this one.
Plus sign multiple personality disorder
One of JS flaws that sound more obvious and silly to me is the plus sign as string concatenator. The classic example 1 + '1' = '11'
sounds wrong to me and to many other people that have to deal with programming. For some reason, it was deemed okay to use the same operator for number sum and string concatenation, which is quite hard to see anywhere else and it is an obvious and perennial source of problems.
Different equalities: two vs. three equals
At the same interview on YouTube mentioned above (here the link again) Brendan Eich remembers that he implemented the weird “equality of different types” in JS because of a suggestion and later he regretted it. Today, attesting the failure of this feature, using non-strict equality (with ==
) in JS is not only frowned upon but issues an error on many linters, so everyone working with JS just agrees on using strict equality (with ===
). Still, whenever one uses an if
statement the comparison is a non-strict one against truthy values, that can be tricky when being caught in its little weirdnesses, like for example the fact it will return false
for an empty string, but true
for an empty array.
Bottom values
Usually values that don’t fit into any “common” type of value (meant for errors and absences) are called bottom values. Crockford tells us that JavaScript has too many bottom values (NaN
, undefined
, null
) instead of the ideal number of one and I agree. He says he uses undefined
for everything that is invalid because it comes built-in into the language (when you don’t specify value on a let
for example) and I understand, but wish I could use null
for that, only because it makes more sense on the point of view of naming, as using undefined
for something that was defined is extremely counter-intuitive.
Also, let’s wonder at the weirdness that typeof null === 'object'
is. There is no good reason for that and this should have been fixed. Long ago.
A value that is unlike any other, really
One of the values mentioned above is a funny one. NaN
is a weird one. Not only it is one of the excessive bottom values mentioned above but it also has a special behaviour, seemingly out of nowhere.
JS has this weird idea of sort of “allowing” division by zero or other invalid mathematical operations or conversions and just outputting a weird specific invalid value called NaN
(not a number). It should just do what most other languages do and fail in more “classical” ways, but instead we have to compare against a value that doesn’t compare against.
The problem with this comparison is that NaN
is not equal to anything, including NaN
itself. The recommended way to infer if a value is NaN
is by using the method isNaN
, but that one has also its own quirks because it coerces type, so the newer Number.isNaN
is recommended instead.
Despite this official recommendation, it seems wise to me to just run from dealing with NaN
like the plague and seek for standardisation of number operations so you never have to rely on it.
Objects, the quirky base of JS
I already talked about prototypical inheritance and how cool it is, but let’s stick to the other side of the coin. Objects in JS are weird for many reasons, starting from the point that objects are what is called in other languages by the name “associative arrays”. When someone developing for a language that uses this name for its key-value pairs stumbles upon JavaScript the confusion will be certain, not only because of JS objects but also because of JS arrays, which we’ll get to in a moment.
One other weird point is that object keys can only be strings. I get why they can’t be other objects, that would have been perhaps too complex to design with all its implications, but how about numbers or, a bit later, other primitives like symbols? Ok, we got Map
s now, but still, at least numbers could be allowed as keys on JS vanilla objects.
Also, why JS doesn’t offer native deep copy or deep comparison of objects? Given how much browser engines evolved in speed and optimisation with time, that would have been a push for the web. I wonder if maybe they didn’t want to stimulate people doing deep copies and deep comparisons carelessly, as people would certainly do, but still, the fact that people do questionable things with powerful tools doesn’t mean we shouldn’t have the tools.
Arrays that are objects, that are arrays, or not…
Crockford mentioned in his “Good Parts” talk about how arrays in JS are different from arrays in most languages. The confusion starts from the fact that arrays in JS are weird syntatic sugars on top of objects, behaving in a very particular way. Being actually objects under the hood means that arrays are identified as objects in typeof
, which is pretty confusing and misleading. This relevant shortcoming was addressed with the later inclusion of a method to identify arrays (Array.isArray
), which is better than nothing but, frankly, far from being a satisfactory solution.
Arrays in JS don’t have any limitation for the number of elements and their values can be of mixed types, unlike in almost every language. Usually arrays are very memory-efficient structures that make use of the number of elements and type for its optimisations, but not in this case: they are way more inefficient that what they could be, ideally.
One interesting quirk of arrays that reveals part of its “unfinished” state is that issuing a delete
command in a array element doesn’t change the array length, only takes the “deleted” element and fills it with an empty element that, if accessed, will return undefined
.
Finally, one note about the native array sorting method: it sorts by default using a non-localised string sort, which is very confusing, especially for newcomers to JS.
Strings at will
The string implementation is JS is, you guessed it, a bit weird, as a string with many characters is kind of an array of strings with one character, but a string is very different from an array in too many ways. One may say the definition of character by today’s standards is sometimes fuzzy due to difference in encoding methods, but this is a problem that JS solved fairly well from the start with an acceptable standard encoding.
What catches my attention is that now JS has three ways to specify strings: single quotes, double quotes and backticks. Aside the fact that backticks are actually different and feature extra functionality, single and double quotes do the exact same thing and many people expected that, at some point, one of them (the double quotes probably) would be deprecated to make things more standardised but, you know, JavaScript…
The DOM and its conflicting directions
The Document Object Model was created by Microsoft on its JScript to represent programatically the HTML tree in JS. It is usually hard to find someone who thinks the DOM is well designed, but that may be because it was created quickly and shipped in an unfinished state because of the tensions from the “browser wars” era. It needed some heavy rethinking but never got it, instead being expanded from this unfinished point.
One of the weirdest aspects of DOM is the event propagation. When a user clicks on a button on top of a div, for example, the click event propagates to the parents. Netscape and Microsoft implemented two different propagation schemes for their respective browsers: Netscape designed a weird and unnatural propagation that goes from parents to children (called capture or trickling) while Microsoft did the right thing and designed one that goes from children to parents (called bubbling). Today we don’t even need to bother about Netcape weird implementation, but back in the day people had to support both and they were required to specify which propagation scheme to use in the method addEventListener
through its third parameter.
Conclusion
All in all, JavaScript is a very successful language and, especially considering its early history, it is a well designed one overall. This doesn’t mean it is perfect, far from that, but once we understand some of those quirks above everything becomes more clear. Still, it would be much better if those aspects simply weren’t there, but that’s the price we get to have this unique retro-compatibility that would otherwise break the history of the Web, while trying to have some acceptable degree of evolution at the same time.