3 big problems with Javascript

For years I had an irrational loathing of javascript. It was to the point where I’d always have it switched off in my browser. To me it was synonymous with bad web design and crappy menus that didn’t ever work in netscape. Then google maps came out and I was completely impressed. They did all that with javascript? I didn’t know javascript could *do* that. So about a year ago I decided to actually look at the language and see what you could do with it. I updated an old cgi script game of mine into a modern javascript game complete with XMLHttpRequest high score stuff. And I was generally very happy with everything. My friend Jim wrote a card game and we developed it into a very nice game site

I really love javascript now, but that doesn’t mean it doesn’t have its faults. I can make a number of little nitpicks but I want to focus on three major deficiencies of the language.

Problem 1: No include directive

This is particularly annoying when dealing with many libraries that also depend on other libraries. It forces you to know all the dependencies up front and hurts the modularity of your site.

I’ve read that an include directive was not ..er.. included in the language because they didn’t want to specify what it meant. The argument goes like this: Javascript is a nicely generic language and so if it was used as a scripting language of a video game (for instance) it would be annoying if the spec said “include” was a URL. I don’t see why that precludes adding the include directive to the language. I would argue that it’s better to have it in the spec with the argument being “implementation defined” and let the w3c guys say that it’s a URL and the game guys make it a virtual file inside some zip archive.

The other argument I’ve heard is that the language designer wanted something nicer than a simple “#include” style of include—something like the class importing of java or a “use” directive of perl. I can understand that. But I would argue that not having anything is much worse than having a simple file/URL based include system.

A third argument I’ve heard is that including files is gross since you have to protect against multiple inclusions with big ugly protection “if” statements like every C header file you’ve ever seen. I don’t buy this either—Objective C solved this 20 years ago. Objective C has “#include” and “#import”. “#include” includes the file verbatim and “#import” includes it only if has not been imported before. It’s very simple and it covers all the cases that might occur.

Problem 2: “this” is wrong in callbacks

Javascript programs, being functional and non-threaded, end up having a lot of callbacks. Especially if you use things like timers and XMLHttpRequest objects. If you pass a function to something and it runs it, your function gets run with all your local variables (since it’s a closure) but the “this” points to something stupid like the callee or the global context (I don’t remember but it’s not your local “this”).

I’m not sure why it does this but I imagine it makes some part of the language designer or language implementer’s life easier. The problem is that it makes every programmers life hell. Yes, it’s easy to get around: just make a local variable and set it to “this”. Look around greenfelt.net and you’ll see variable named “me” and “_this” littering the code. But I shouldn’t have to do that at all—the language should just do the right thing (maybe that’s the perl side of me talking).

My argument against this is that it is *never* what you want as a developer. *Never!* Why force every program to have stupid little work arounds just because it makes the spec of the language cleaner at some level? I think the spec writers and language implementors need to suck it up and take one for the team here.

How much time have I spent debugging a problem caused by the stupid “this” pointing to the wrong context? Days, I’m sure.

Problem 3: Inheritence sucks

The javascript object model is simple. I generally like it. But its simplicity causes problems when trying to do inheritence. I looked around for ways to do inheritence and everyone seems to have a different way of doing it. And all the methods seem to have some drawback. In the end I ended up settling on a method that seemed to minimize code, but it’s still ugly:

The base class:

function baseclass(a,b) {
    if (arguments.length) this.init(a,b);
}
baseclass.prototype.init = function(a,b) {
    ...
}

And the inheriting class:

function subclass(a,b) {
    baseclass.prototype.init.call(this,a,b);
    ...
}
subclass.prototype = new baseclass();

This allows for “new baseclass(a,b)” and “new subclass(a,b)”. But there’s a lot of scaffolding there. It really needs some good syntactic sugar.

From what I’ve read they’re trying to rectify this in the new version of javascript being developed.

I can only hope they also fix the other 2 problems.


Addendum

[Mon Mar 6 01:09:49 PST 2006]

Thanks to a comment from Pasha I’ve been experimenting with a different way of working around problem #2 using a the apply() function:

function wrap(context, f) {
    return function() {
        f.apply(context, arguments);
    };
}

Usage:

div.onclick = wrap(this, function() { ... });

I’m liking this better than my previous work around, the “var me=this” closure variable trick. It’s better because it actually makes “this” be correct and so the inner code doesn’t have to use a different variable name. The downside is you have to pass “this” into the wrap() function—it would be better if it could climb up the call chain and get “this” out of whatever context came before it.

11 thoughts on “3 big problems with Javascript”

  1. Nice article. I completely agree. And here’s one more…

    Problem 4: Not enough polymorphism

    I can’t define equality or comparison for my own types. I’m stuck with JavaScript’s default behavior, which more or less assumes everything is a string. There’s no standard protocol for hash codes; I can (ab)use objects as hash tables, but I can only use strings as keys.

    I can’t define an object’s behavior in a boolean context.

    Callable objects are only made possible by treating functions as objects, never the other way around.

    I can’t redefine the behavior of “for-in” loops, and I can’t override indexing on my own objects so that they can participate in regular “for” loops without code changes. There is no standard iterator protocol aside from array indexing.

  2. Re: 2, you may be dealing with Internet Explorer issues, not language spec issues. In Mozilla-related products, “this” behaves more reasonably in many cases.

  3. Lots of people end up solving these problems when they undertake any non-trivial amount of JavaScript. It’s really too bad everyone has to duplicate this effort.

    For what it’s worth, here’s an module that contains examples of solutions to each of these problems:

    http://divmod.org/trac/browser/trunk/Quotient/xquotient/static/js/mailbox.js

    ”// import …” deals with dependencies, the “self” parameter to methods fixes the brokenness of “this”-scoping, and the “subclass” method of “Class” objects (of which Nevow.Athena.Widget is one) makes inheritence a breeze.

  4. for problem 2 you can use “apply” to solve it:

    
    function create_callback(method, object) {
        return function() {
            method.apply(object);
        }
    }
    

    i will leave passing the args to the callback as an exercise for the reader.

  5. Keen article.

    Are you familiar with the way the Prototype library (prototype.conio.net) handles class constructors? It’s essentially the same, but a little more syntactically sugary. It’s still a far cry from useful inheritance.

    You’re not really achieving any inheritance benefits with your scheme, since you can never pretend that the subclass actually owns the methods of the baseclass: you’re always referring to the baseclass explicitly when you call a method. That means you don’t get any subtyping abilities (i.e. you can’t just use a subclass object wherever a base object is expected).

    Your code does a nice job of pointing out the problem with using normal javascript constructor functions as the initializing functions for your objects. You have to instantiate the object if you want to use it as the prototype for another object, but you really don’t want to run the initialization code in those cases, so it’s best to have the constructor do nothing except (when desired) call the true init function.

    Agree that the dynamic scoping of ‘this’ is a foolish default behavior. Prototype has a nice function called bind to handle that problem.

    I’ve been working on these problems for a few months now and I think I’ve come up with an extremely usable solution. You can find a demo at http://groupspace.org/devben/prototype/demo.html

    The first code snippet is the canonical Prototype syntax for class creation & inheritance; the second is my improvement. The third and fourth are experiments in privacy restrictions.

    Note the function this.sup—it resolves to the superclass’s version of whatever method called this.sup. Handy!

    Cheers,

    Ben

  6. I think the Prototype style “extends” function can be an acceptable work-around. I don’t really like the idea of javascript copying all the prototype elements around, but it does the job with minimal syntax. It really should be part of the language though. It does force 2 layers of indenting on all the class functions which rubs me the wrong way for some pedantic reason.

  7. I agree with your article. These are the shortcommings of JavaScript, but thankfully theres ways around them.

    The Dojo Toolkit has some neat ways of handling these tricks.

    For code loading: dojo.require(‘dojo.xml.*’);

    or, use the JS compiler to compress all required files into a single JS.

    Problem 2 could be handled nicer in dojo. Many functions in dojo ask for two parameters (object, function) – where the second is the string name of the method of object. I would prefer to use the wrap method you defined above.

    Inheritance, although ugly, is fast and clean:

    function NewWidget() {
      dojo.widget.Widget.call(this);
    }
    dojo.lang.extend(newWidget, dojo.widget.Widget);
    

    The Prototype toolkit also solves these problems in their own way. Your mileage may vary.

  8. Problem 2 is NOT a problem but a core principle of javascript (and other dynamic languages such as self etc.).

    “this” always being evaluated at call time to be the context of the function (as opposed to the semantic scope of variables, determined by the position of the var statement) is the part, that makes Javascript dynamic.

    Have a look at prototypes bind() function (or the earlier mentioned built-in apply on which it bases).

    It makes writing callbacks much more concise and clear.

Leave a Reply

Your email address will not be published. Required fields are marked *