Absurds of JavaScript, part umpteenth (with a twist)

Do you know Wat? If not, you should watch it right now. It’s JavaScript in a nutshell.

Okay, maybe it’s not that bad.

Oh come on, it IS bad.

If you’re not familiar with JavaScript, it’s an object-oriented language. Oddly enough, it doesn’t use class-based inheritance. JavaScript uses prototype-based inheritance. I don’t really want to dive into prototypal inheritance in this write-up, so I’ll just cover basics of object instantiation in JS.

Objects in JavaScript are basically maps, or associative arrays if you prefer. It’s actually quite convenient. You can represent any object in a JSON notation:

{
    firstname: 'John',
    lastname:  'Doe',
    age:       21
}

If you need to create a method, you just assign anonymous function to a property. Like this:

{
    price: 20,
    getPrice: function() {
        return this.price;
    }
}

Pretty handy, eh? This is the part I like about JavaScript. JSON is awesome. You can use JSON directly in code, and for example, instantiate objects like this:

var obj = { name: 'John' };

This is nice, but of course you don’t want to copy-paste JSON every time you instantiate an object. In class-based languages you would create a class, with a constructor probably, and spawn concrete objects of that class anywhere you’d like to using the new keyword.

There’s a new keyword in JavaScript too. It should be followed by a function call. A new, empty object will then be created and that function will be called with variable this injected into its scope and bound to the newly-spawned object.

Let’s see an example. If you execute this code:

var pair = function (a, b) {
    this.x1 = a;
    this.x2 = b;
};
var obj = new pair('hello', 'world');

You’ll end up with obj being initialized with an object like this one:

{
    x1 = 'hello',
    x2 = 'world'
}

It’s a bit weird at the beginning, but it actually works and maybe even makes sense. (I’m not sure yet, ask me in 10 years from now.)

That’s it for the JavaScript OOP 101. Here comes the worst part.

Let us define a constructor function f.

var f = function () {
    this.str = 'it works!';
}

We can instantiate object of type f like this:

var obj = new f();

But what if I forgot to call the function, ie. I will miss final parentheses?

var obj = new f;

Obviously, this will fail with a syntax error… No, it won’t.

These two lines are exactly equivalent:

var obj = new f();
var obj = new f;

JS parser will implicitly call the function.

Implicit behavior is generally undesired in programming languages, but this one is beyond every stupid implicit behavior I can think of. Bloody hell, it calls a function just by receiving its definition! Why would you do that?

Actually, Pascal did that: you could call argument-less procedures and functions without parentheses. But Pascal wasn’t using them to instantiate objects. JavaScript isn’t even consistent: f isn’t equivalent to f(), but new f is equivalent to new f(). This is lunacy.

But wait, there’s more.

What if the constructor accepts arguments, huh? Like this one:

var g = function (x, y, z) {
    this.x = x;
    this.y = y;
    this.z = z;
}

Correct instantiation would be:

var obj = new g(1, 2, 3)
/*
 *  obj == {
 *      x: 1,
 *      y: 2,
 *      z: 3
 *  }
 */

What if, once again, I forget to call the function?

var obj = new g;

Well, JavaScript will happily create a new object.

{
    x: undefined,
    y: undefined,
    z: undefined
}

What happened here?

In JavaScript you can, uh, call function with less arguments than it expects. It won’t issue a warning nor error, it’s perfectly fine. Remaining arguments are simply assigned a special value of undefined, which is also a value of nonexistent variables.

In the invalid call g function is called with no arguments, so all fields of the object are assigned with undefined.

That’s right. If you forget to call object constructor, JS will not only happily do it for you, it will also fill in constructor arguments.

It’s a bug hatchery right there, and we shall kill it with fire. But…

Sadly, there’s a good part to this madness.

So far you should realize that every function in JS is basically an anonymous function. (There’s also syntax for creating named functions, but it’s pretty much obsolete.)

var f = function () { console.log('test'); };

Okay. This is a function call, right?

f();

If f is just a variable with an anonymous function assigned to it, then we should be able to substitute f in this function call with an actual function and it should be executed. So this:

var f = function () { console.log('test'); };
f();

Should be equivalent to this:

(function () { console.log('test'); }) ();

And in fact, it is. This trick is known as IIFE and is commonly used to create isolated scope. (Yes, you have to wrap the function in parentheses or do some other bizarre tricks for weird reasons. This is all you should know if you don’t want to lose last bits of your sanity.)

Okay then. So, taking this reasoning even further, you can write this monstroity and it will actually create an object:

var obj = new (function () { this.str = 'hello'; }) ();

Your future self will hate you when he’ll have to find bugs in such code, but you definitely can write it.

So, when you call a function immediately after creation, you have to wrap it in additional parentheses. This is no good for readability.

But if you don’t have to explicitly call it, you can leave these parentheses out. It’s four parentheses less in total, and less parentheses == cleaner code..

This allows us to write factories like this:

var WidgetFactory = {
    createWidget: function(width, height) {
        return new function () {
            this.width = width;
            this.height = height;
            this.is = 'madness';
        }  // this anonymous constructor will be implicitly called
    }
}

So at the end of the day, this quirk may come in handy sometime…

I sincerely hope that one day I will wake up in a better world, with a robust client-side language for the web implemented in every single browser.

 

Wojtek

You have a syntax error in `var obj = { name: ‚John’; }` :-)