JavaScript is an interpreted language with a syntax resembling that of C++ and Java. It is not an interpreted version of Java, it is not a dialect of Java, it no more to do with Java than it has to do with C++. Why the name, then? Marketing. When Netscape invented JavaScript in 1996, they called it LiveScript1. At the time, Sun Microsystems was pouring millions of dollars into marketing their cross-platform implementation of their Java language. Netscape's marketing people borrowed the name to get a free ride on Sun's massive PR budget. That's the only connection. Both Java and JavaScript borrow a lot of syntax from C++ for the same reason that C++ borrowed so much from C: People know it already. It makes for a shallower learning curve.

To the extent that most people bother to learn JavaScript, it's not easy to see the differences between it and those other two languages. We're about to learn about a very big difference indeed: The way classes are defined. It's a little bit weird, and it's painfully "counter-intuitive"2 for a C++ programmer, but it's got some neat features. We'll discuss a few superficial points relating to what a "class" is and where they come from. We will assume that you are at least casually familiar with at least one mainstream object oriented programming language. I am not going to try to persuade you of the worth of object-oriented programming. You can write code in Apple II Basic with line numbers, for all I care. It's a free country.

  1. Class Instances

    In JavaScript, all variables are re-bindable references to objects. Objects have types and literals have types, but variables don't have types. It looks like a variable is a "thing", but it's not, it's a reference to a thing. This is an important distinction sometimes: An uninitialized variable is not equal to zero or the empty string; it's "undefined". So initialize the damn things.

  2. Object

    The "generic class" in JavaScript is Object. Object has a few members which any other class gets also:

    prototype
    constructor
    toString()
    valueOf()

    Those are the ones that Microsoft and Netscape both support. Netscape has a few others. toString() is called when there's a need to represent the object as a string, etc. There's another point which won't make sense for another few paragraphs, but it should be mentioned here because it's a property of the Object class: Members added to Object.prototype are added to all class "prototypes".

  3. Virtual Functions

    JavaScript doesn't have those, but in a language where all function arguments are optional and with no static type checking, it's not really applicable. If you call member function foo() on an object, well, if it's got a function by that name, it'll get called.

  4. Dynamic Object Members

    Normal

    Classes in Java and C++ (and ObjectPascal, etc.) are carved in stone at runtime. You define a class in terms of what data members and member functions it has, and that's what it is, permanently. Every instance of that class has exactly those data members and those member functions and no others. The same goes for local or global variables and functions: You declare it before you compile, or else it doesn't exist. They're both statically typed compiled languages, and that's the way those things work: Early binding and all that.

    JavaScript is a different animal. You can add members to an object -- a class instance -- at run-time. There are two different ways to do this. The first is just what you'd expect in an "object-oriented" interpreted language:

        var foo = new String( 'blahblah' );
    
        foo.blah = true;
    
    

    Now we have an instance of String which has all the usual members that String instances have, but also has another member named blah. We added it by assigning to it. This doesn't work in reverse: If you reference a new member before assigning to it, that's an error.

    Weird

    The other way to add instance members at run-time is slightly strange.

        var foo = new String( 'blahblah' );
    
        foo[ 'blah' ] = true;
    
    

    Yes, every object is an associative array (this means that there is no associative array class per se; every object is one already). That's the only sane way to implement "object properties" behind the scenes, but JS goes further and openly admits it to the user of the language. Therefore, much like Java's reflection API, you can examine this stuff at runtime without any prior knowledge of the object. Given eval, this means it'd be trivial to write generalized object persistence code.

    WARNING: Don't ever use a JavaScript Array object as an associative array. In fact, the closest thing to a "safe" choice is Object, and even that has a lot in common with Russian roulette. See footnote3.

    There is one problem here: JavaScript is interpreted, so all of the "standard library" classes are "built in" classes implemented in some compiled language inside the interpreter. Some implementations (that of Microsoft, for example) treat those differently from user-defined classes. The following code will generate a table showing all of the members of the object foo if you run it in Netscape's interpeter. Microsoft's interpreter will only iterate through the members you've added yourself. It's a bummer, but that's life. If you want persistent objects, you'll have to special-case all the built-in classes4.

        //  This has to be run in a web browser, but that's probably 
        //  the only JavaScript interpreter you've got, anyway.
        var foo = new RegExp( /[0-9]/, 'g' );
    
        foo.blah = true;
    
        document.write( '<table>\n<tr><th>Name</th><th>Value</th></tr>\n' );
        for ( p in foo ) {
            document.write( '<td align="right">' + p + '</td><td>' 
                            + foo[ p ] + '</td></tr>\n' );
        }
        document.write( '</table>\n' );
    
    

    This makes more sense when you consider the fact that the language has an eval function.

    Why would you want to do this? Take for example DHTML. Assume that you've got a web page with a number of objects in it, and you want to make them all move around so as to annoy the user. Each of them will have a horizontal and vertical velocity. You can add those to each object as members, just like you'd do if you had written the whole thing and all the classes from the ground up in C++. The alternative would be maintaining some of the state for each object in the objects themselves (the built-in members), and other state information in a different place entirely. Then you've got to associate the two, and so on: It's ugly and inconvenient. Furthermore, while JavaScript supports only a weird kind of inheritance, you can do something a lot like virtual functions: If (just for an example) half of your objects bounce and the other half don't, you can give each object an appropriate moveMe() member function on startup, and from there on in you won't have to worry about which is which. This is part of what we call object-oriented programming.

  5. Dynamic Class Members

    You can also add members to an entire class at run-time. This is seriously cool. Every class is itself an object, with two interesting properties: prototype and constructor. prototype is the one that lets us work our magic here. It defines what members the class has. Say we're doing SQL queries, and we need to escape apostrophes in quoted strings: SQL quotes strings with single quotes, so if there's an apostrophe in the middle of a quoted string, that's an error. Therefore, we always need to fiddle a string before using it in a query. We can do that with a member function:

        function String_SQLSafe() {
            //  "this" must always be used explicitly.
            return this.replace( /\'/, "''" );
        }
    
        String.prototype.SQLSafe = String_SQLSafe;
    

    Now every String object in your program has that member function5. The name is absolutely arbitrary; I just called it [class]_[funcname] for clarity. This isn't like Visual Basic where the underscore there has meaning. It could just as easily be named dumptruck. Naturally, this works with data members as well, because member functions are data members. Functions in JavaScript are objects, just like any other object (and just like classes themselves) (shades of LISP, eh? All good programming languages are syncretic). If you reference a function without parentheses on the end, you're referencing it as an objects; if you add the parens, you're calling it.

    Very Cool: Members added to Object.prototype will be added to all class prototypes.

  6. Defining a Class

    We've looked at varying ways of adding members to an existing class. Now, let's define a class of our own. It's so simple that at first one has a tendency not to believe that anything has actually been done. The creepy thing is that it doesn't all happen in one place. We'll call our class edward.

        //  This is the constructor. It looks just like any 
        //  other function, except that it doesn't return anything.
        function edward( heightarg, widtharg ) {
            this.height  = heightarg;
            this.width   = widtharg;
        }
    
        //  This is where we decide that edward is a class.
        edward.prototype.height     = 0;
        edward.prototype.width      = 0;
        edward.prototype.getArea    = edward_getArea;
    
        function edward_getArea() {
            return this.height * this.width;
        }
    
        //  Let's create some little edwards.
        var my_ed        = new edward();
        var my_other_ed  = new edward( 4, 5 );
    
    

    And that's edward: Any edward instance is guaranteed to have height, width, and getArea properties, and when it's created we can guarantee that getArea will be a function. Unless we do our own type-checking in the constructor, a user of the class could pass in absolutely anything for the height and width arguments. They could be functions, the body tag from an HTML page, anything. After it's been created, the user could even assign arbitrary crap to the getArea member: It could be a different function, which might be useful. It could also be something stupid, like the letter 'W'. Further, somebody can come along and change edward's prototype.

    It all looks a little bit quick and dirty, from a language design standpoint. Other interpreted object oriented languages (e.g. Python) will let you add members and so on, but when they define a class, they have something clearly identifiable as a class definition (the impending ECMAScript version 2 will have those, too). On the other hand, since you can mess with the "prototype" of a class after it's been defined, why add a second syntax for the same thing, hm? Well, to save typing, for one thing. They've got a static array initializer syntax for that reason. Either way, it's really just an aesthetic issue.

  7. Inheritance

    This section has been updated to reflect reality, and moved so it comes after the parts you'll need in order to make sense out of it. Mea culpa and thanks to ariels for hassling me about it.

    JavaScript has a sort of inheritance based on prototypes. A constructor in JavaScript has a prototype property. When you call a constructor, a new object is created which is a duplicate of the prototype object (but there's more to it than just that, because changes to a class's prototype will affect instances which already exist). The constructor "is" the "class". By default, a prototype is just an Object instance. As we've seen, you can add members to it. You can also replace it with any arbitrary object instance. That's how you subclass a class: Assign an instance of it to the prototype property of another class.

        function Foo() {
        }
    
        Foo.prototype.foo   = 'foo';
    
        function Bar() {
        }
    
        Bar.prototype = new Foo();
    
    

    Now if you call new Bar(), your new Bar instance will have a foo member equal to 'foo'. There's one weird bit, though: Objects in JavaScript have a constructor property. You can determine the type of an object by looking at obj.constructor (you can also use the typeof operator, but all it knows about is 'number', 'string', 'function', and 'object'). In the code we've got above, the constructor property of a Bar instance will be Foo, not Bar. So here's what you can do to straighten that out:

        Bar.prototype.constructor = Bar;
    
        //  Now, (new Bar()).constructor == Bar
    
    

    Netscape's JavaScript (as of v1.5) adds a non-standard extension: They give functions a name property, too6. For an instance b of Bar, b.constructor.name == 'Bar' (assuming that you've gone back and assigned its constructor to itself).

    This whole inheritance thing is a little bit ugly. The vibe is more "workaround" than "language feature". I guess you might say that JavaScript permits inheritance, but doesn't support it quite as enthusiastically as we might like.


It's a topsy-turvy world, isn't it? There's very little you can be sure of. This is why you don't use late-binding languages for really significant projects, but it's also why they can be very handy for little things where the ability to modify existing code can save much time and complexity.




Notes

  1. In fact, the mechanism for creating an interface between JavaScript and Java class instances in Netscape's web browser is still called "LiveConnect".

  2. "Counter-intuitive" is one of the most nearly meaningless words in the English language. "Counter-intuitive" simply means that you haven't done it that way before, which is a property of the observer rather than of the thing described.

  3. Why you should never use a JavaScript Array object as an associative array:

    There's nothing to be gained, for one thing: The associative array features are equally present in every other object, too. There is, however, something to be lost: Imagine if you happen to use the word 'length' as a key. That's the name of a property of the Array class. Remember: All the keys are in fact the names of properties, and vice versa. Depending on implementation (Microsoft's version will let you do this to built-in properties of instances of built-in classes), you'll end up setting the Array object's length property to something arbitrary and strange. This will break the object and your script will crash for reasons that make no sense to you. The same goes for any property of any object. The Object class is your safest (or least dangerous) bet, simply because it has fewer properties than anything else. You're still playing with fire, though. For associative arrays, the only safe thing to do is prepend an underscore to every string key before assignment and again before lookup. That way, there can't be any collisions. It's a hassle, though.

    I'd like to advise writing a quickie AssocArray class with set( key, val ) and get( key ) member functions which handle the prepending for you. The problem with that is that you have to remember not to mix your accessor functions with the for ( k in ary ) { /*do stuff*/ } loop syntax that's the only reasonable way to traverse an associative array (but which is nearly useless, because it'll give you stuff like member functions along with whatever you're storing in the object).

    This is a very serious problem with the language, in my opinion. The feature that's responsible for it is clever, but in a very shallow way. Somebody just wasn't thinking ahead when it was designed.

  4. The other side of the coin is that Microsoft's associative array implementation is considerably faster than Netscape's. Fortunately, the source code for Netscape's interpreter is available and it would be relatively trivial to hack in something efficient. The STL map template would probably do just fine. That won't do you any good if you're running your code in end-user browsers, but for all I know, they've fixed it already. I haven't given the current version a good workout. The point is that if you're shipping JS as an extension language with some other product, you can hack on the interpreter all you like.

  5. Like you'd expect from a language where a function is a first-class object, JavaScript supports anonymous functions. I didn't want to muddy the waters above, but a cleaner and more pleasing way to add a member functon is to do like so:

        String.prototype.SQLSafe = function() {
                return this.replace( /\'/, "''" );
            }
    
  6. There's a weirdie with that one: Anonymous functions created with the function keyword get an empty string for their name value, while anonymous functions created with the Function constructor get 'anonymous' for a name. Go figure.




References:
The ECMA-262 standard
Netscape's Client-Side JavaScript Reference
Painful experience with versions 1.3 (Netscape Navigator v4) and 1.5 (Mozilla) of Netscape's JavaScript implementation.
Painful experience with the JavaScript implementations in MS Internet Explorer 4 and 5.



Naturally and as usual, I'd be grateful to be clued in about any errors, confused bits, or infelicities in this writeup.

Log in or register to write something here or to contact authors.