Caffeinated Simpleton

JavaScript is Not Perfect

After posting cobra, one of the things I heard most was “don’t try to make JavaScript be something it’s not”. This is good advice, but I feel that in this case it was given too hastily. Cobra did not come out of a desire to make JavaScript more like Python, even though that was the result. Cobra came out of careful consideration of how to make JavaScript better.

Problems with JavaScript

I think most people who happen upon this lonely corner of the internet are already pretty familiar with the flaws of JavaScript, but I’ll mention a couple of the most glaring faults briefly.

Scope

JavaScript’s scoping rules are dumb. Variables default to the global scope unless told otherwise and the this keyword, which is supposed to point to the current instance, actually points to the global object unless told otherwise. These scoping rules cause huge problems for beginning JavaScript programmers and trip up the most experienced programmers if they aren’t paying attention. Since these odd scoping rules default to the global object, and not to some error state, errors can go by unnoticed for long periods of time.

Object Syntax

JavaScript claims to be a prototypal language. It lies. It’s a language that also has prototypes. In a true prototypal language, there are objects. Objects can be derived from other objects, either by copying the parent object or by linking to it. JavaScript does something similar with its objects’ built in prototype attribute. Its prototype points to an object that it replicates the behavior of. However, it doesn’t replicate this behavior until after you create a new object. Let me demonstrate with some unsupported features of JavaScript:

//This is my base object. It's a pretty simple 4 step dance. Dance = { danceAround: function() {}, steps: 4 } // Let it be known that I wouldn't know what a tango was if I saw it. // (Unless there was a rose in somebody's mouth, of course) Tango = { steps: 34, dip: function(){} } // Now we pull some true prototype magic. Tango.__proto__ = MyDance; // This works then: Tango.danceAround();

That’s true prototypal behavior. There’s no “new” keyword, just behaviors that can be stolen by other objects. Unfortunately, JavaScript was thrown together in (I believe) about 15 minutes, minus a 5 minute coffee break. The writers realized that people were going to flip if it didn’t resemble the popular languages of the time, so they implemented a prototype-based language, pulled the prototypes into a separate property, and added a “new” keyword. This led to syntax like the following:

//This is my base object. It's a pretty simple 4 step dance. Dance = { danceAround: function() {}, steps: 4 } Tango = function() { this.dip = function(); this.steps = 37; } Tango.prototype = Dance;

That’s not quite as pretty. Not only is it not as pretty, but there are some fundamental flaws. The dip function gets recreated every time a new Tango object is instantiated. This isn’t a big deal most of the time, but once a year the king has a ball and all of a sudden you have 1000 partners tangoing about, and with them, 1000 identical copies of the dip function.

Another flaw is that the prototype is not in the lookup path of the object until a new instance is instantiated. In this example, Tango.danceAround is not defined. This is because prototypes are not applied to objects until a new instance of the object is instantiated. A “class” in most languages is a definition of object behavior. Instances of a class are objects that behave as defined by the class. This is very close to how prototypes behave in JavaScript.

To summarize, JavaScript isn’t quite a prototype based language, it isn’t quite a class based language, and the syntax for doing either is ugly (for more on the ugliness, check out one of my previous posts).

Fixing the Problems

Fortunately, JavaScript is awesome in most ways. It’s so flexible that fixing the issues I’ve spelled out above is no problem.

Fixing Scope

You can’t entirely fix JavaScript scoping. Local variables which aren’t declared with var become global, and there’s nothing you can do about that.[1. If you don’t care about standards or cross-platform compatibility, check out FireFox’s built in parent attribute, which lets you mess around with enclosing scopes.] You can, however, fix the this object. You can wrap any given object method in another method which asserts that its this will be set to a specific object.

MyDance = { danceAround: function() { console.log(this.cheer) }, cheer: "WHOOO!" } MyDance.danceAround = function() { MyDance.danceAround.apply(MyDance, arguments); }

This is called binding, and it makes this be what you would want it to be in most instances. My initial thought was to just bind all object methods to their instances at the time of instantiation. However, I don’t really like this idea. For one thing, it changes the language. For another, some libraries (I’m looking at you jQuery) like to mess around with this. If this is expected to be something, I do not want to change that. What I needed was a shadow this, a variable that was always present and always pointed to the instance of the object. Luckily, there was an easy solution. Python always passes its instance object as the first parameter of any method of a class. I could replicate this behavior easily using essentially the same binding code and have the code look familiar to Python programmers everywhere. So I did. Every instance method in Cobra has “self” passed to it as the first parameter, which is automatically guaranteed to be the instance, no matter what. No binding required.

Fixing Object Syntax

It is fairly easy to get JavaScript to behave like a true prototypal language. However, I don’t much care for true prototypal behavior since it still leaves an ugly syntax. My solution was to create a “Class” object that will implicitly apply prototypes.

Instead of:

MyNewThingy.prototype.doSomeStuff = function () {}; MyNewThingy.prototype.doMoreStuff = function() {};

We can do:

MyNewThingy = new Class ({ doSomeStuff: function() {}, doMoreStuff: function() {} }); These end up being exactly the same, except the latter is clearer and cleaner in my opinion.

Inheritance is a bit tricky in the first case. To achieve prototypal inheritance, I have to do some magic.

Base = { basicStuff: function() {} } ThingyPrototype = new function() { this.doSomeStuff = function () {}; this.doMoreStuff = function() {}; } ThingyPrototype.prototype = Base; ChildThingy.prototype = ThingyPrototype;

Now ChildThingy inherits from Base and has some of its own functions in its prototype. Cobra takes care of all of this for you:

Base = new Class({ basicStuff: function() {} }); ChildThingy = new Class({ __extends__: Base, childsOwnStuff: function() {} });

Again, I think this is a lot clearer and cleaner.

If you put both these fixes together, you end up with Cobra, which you can read all about here.

To wrap things up, augmenting JavaScript to fix its flaws is not a bad thing. The question is what to add. I haven’t used Cobra for anything yet, but it’s my current pet project. We’ll see if it really makes JavaScript that much more pleasant.

comments powered by Disqus