I have used JavaScript of and on for many years but in the last three years I have used it seriously for building a large client-side web application. During that time I have push many of the boundaries of JavaScript and uncovered some fundamental issues with the language.
One of the most common complaints about JavaScript is that its object system is broken. This is almost always becuase of a misunderstanding of what the language is. JavaScript is not an object-oriented language, it is a prototype language. What does this mean? It means that I can change the “object hierarchy” of an existing object at runtime by changing the object’s prototype. This is by design and isn’t one of the real problems–it just requires a different way of thinking.
No, JavaScript has a much larger problem and it is one that cannot be fixed without changing the language. Because it is a prototype language, the various stages of the build process (the compiler, the runtime engine, the code emitter) cannot know what a particular token means until the token is executed.
Let me explain: when you enter a token like foo
into the code this is a directive to the runtime system to look up the current definition of the token at the moment it is executed. The system cannot determine that the token has the definition of say 123
and use that information the next time the code is executed because the value could change. Even the type could change. Now if this is a true variable like the number of items in a list then it works as expected but if this is a reference to a function to call then the function that is called can change from one execution to the next. Now I’m not talking about the restricted changes that OO allows like overriding and overloading I’m say that there does not have to be any relation between the first function and the second; they can even have different parameter lists. This makes the program literally impossible to reason about.
Let that sink in: it is impossible to reason about a program. If any function invocation can be changed anywhere in the code to any other completely unrelated function then you have no way of knowing what function is really being called. One popular way of “customizing” a library class is to replace the existing definition of a function with another, possibly wrapping the original or even completely replacing it. The library writer will not know that this happened and the contracts could be broken. So even if the library writer has done everything right, it can still fail in weird ways.
Now there are way to mitigate these problems, you can hide internal function and variables and only call the external interface for notification style calls where you don’t care what they do with the values but these systems complicate the code and only to overcome a shortcoming of the language.
The newer specs are adding features to help with this but they aren’t widely available nor used yet. I will talk about these in a future post. It would be nice if we could start over and move to something like Dart but unfortunately we have too much legacy code to be able to change now.