Bill's Computer Circus
Don't get caught with your system down.
NOTICE: This web site may not render correctly in older browers like Internet Explorer 5.2 for the Mac. May the gods help you if you are using Internet Explorer on any machine! Otherwise, if this site does not look right on your browser, please let me know what browser you are using (and what version and on what computer). Thanks!
"Visual Basic makes the easy things easier. Delphi makes the hard things easy."
-- unknown
Tuesday, December 20, 2005
 
JavaScript Object Underpinnings
JavaScript objects are somewhat of a mystery, though the exact way they are implemented is not important to know how to use them. But I continually find interesting aspects about them.

Study the following code. Print it out and read it before going to bed if you need to (or if you just need to get a good night's sleep). I'm going to pick it apart in the text that follows it.

NewA.prototype.f = function() {
alert("NewA.prototype.f()");
}
function NewA() {
this.a = 1;
this.b = 2;
this.f = function() {
NewA.prototype.f();
alert("NewA instance f()");
}
}

NewB.prototype = new NewA();
NewB.prototype.inherited = NewA.prototype;
NewB.prototype.f = function() {
this.inherited.f();
alert("NewB.prototype.f()");
if (this != NewB.prototype) {
this.f1();
}
}
function NewB() {
this.b = 3;
this.f1 = function() {
this.inherited.f();
alert("NewB instance f() -- a="
+this.a+", b="+this.b);
delete this.b;
alert("b = "+this.b);
}
}

// -- Main code --
var oA = new NewA();
var oB = new NewB();
alert( "Calling oA.f()" ); oA.f();
alert( "Calling oB.f()" ); oB.f();
alert( "Calling NewB.prototype.f()" ); NewB.prototype.f();


.
I think this example illustrates some interesting aspects of objects and "inheritance" as seen from the perspective of JavaScript. It is ever more clear to me that JavaScript's object-oriented paradigm is not as complete or straight-forward as in other languages...like Delphi. But then I think Delphi is king in that area, anyway, so perhaps I am biased.

Let's first examing object NewA. NewA does not inherit from anything, so it inherits the Object type properties by default. I expanded the NewA object by adding a function, f(), to its prototype. I also defined two instance properties, a and b, within the constructor. I then also defined an instance method, f().

By doing this, however, the instance method f() overrides the static method f() defined in the prototype. Therefore, any instance of the NewA object will execute the instance version of f(). Note, however, that within the instance version of f(), a call is made to the prototype version of f(). In the main code, an instance of the NewA object is created, oA, and then a call is made to oA.f().

The result of the line that calls oA.f() will be three alert windows that pop up in sequence in the following order:

Calling oA.f()
NewA.prototype.f()
NewA instance f()

.
This indicates that the code in both the prototype version and the instance version of the f() method were called. Remember that in JavaScript, properties defined in the prototype are available to all instances of an object. So, for example, in the following code:

Obj.prototype.a = 3;
function Obj() {
this.b = 7;
}
var obj = new Obj();
alert(obj.a);
alert(obj.b);

.
two alert() calls display the values of a and b, which in this case would be 3 and 7, respectively. The property a is a "class" property, or rather, a property of the object's prototype, but because obj is an instance of that property, it "inherits" the value of a (which is really to say that the static value of a in the prototype is visible to the instance as if it was a property of the instance).

However, if you look back at the code for the NewA object, the prototype version of f() was reference explicitly as NewA.prototype.f() and not through the this property. This is because the prototype's version is not part of the instance of the object and, therefore, cannot be addressed through this. On the other hand, this.f() refers to the instance version of the method. If one were to call this.f() from within this.f(), this would create a recursive condition which, in this case, would continue until the browser ran out of memory, likely causing it to crash.

Now let's look at the NewB object. NewB inherits from NewA. This is accomplished in the statement NewB.prototype = new NewA();. The interesting thing here is that you might think that NewB.prototype is now an instance of NewA, but this is not entirely true. If you were to call NewB.prototype.f(), what do you think would happen? You might think it would invoke the instance version of NewA, but it doesn't; instead, the static version is called. Only the static properties and methods (i.e. those defined in NewA.prototype) are available to NewB's prototype.

Prototypes do not carry instance properties (directly, anyway) and are thus not aware of them. You cannot access a or b from the NewA object through its prototype - only through an instance of the object can they be referenced, because they are not defined in the prototype. However, if you reference a prototype, directly, such as to call NewA.prototype.f(), the method has a this property available to it. Although, in this case, even if it is called from within an object's instance, this refers only to the prototype.

This is why in NewB, which also defines an f() static method, there is a test to see if this refers to NewB.prototype before referencing this.f1(). Although, this is going to take some additional explanation to describe what all is going on here.

Let's back up a little and look at the rest of NewB. NewB defines a static property called inherited that I created as a convenience to refer to the prototype of the inherited NewA object. This makes it a little clearer within the code of NewB methods when referring to static methods from then inherited object. Again, keep in mind that only the static properties and method of the inherited object can be accessed.

As mentioned, a static method f() is defined. The first thing it does is call the inherited static f() method from NewA. It then displays a message indicating it is executing the static version of NewB.prototype.f(). Then there is the test I mentioned to determine if it is safe to call this.f1(). The f1() method is defined as an instance method for NewB within its constructor. The reason this test is here is because the static f() method can be invoked either through an explicit call to NewB.prototyp.f(), or through a an instance of the object, such as oB.f().

The f() method can be called through the instance through the normal mechanism of instances "inheriting" all the static properties and methods available from the prototype. There is no instance version of f() defined, so the prototype's f() is called. When invoked through an object instance, the this property refers to the instance of the object, and not to the prototype. Therefore, since the NewB object does define an instance method, f1(), this method is available through the this property and can be called. If NewB.prototype.f() is invoked directly, no such f1() method is available.

Now, remember that a and b from NewA cannot be accessed through NewB.prototype. However, any instance of NewB will inherit the instance properties of NewA and will appear within the NewB instance. However, NewB turns right around and defines a new b instance property of its own, which overrides (or hides) the value inherited from NewA. This now makes it impossible (without deleting the b instance property from the NewB instance) to access the value from NewA, as method f1() will illustrate.

The f1() instance method calls the inherited static method f() method from NewA, then displays the values of a and b, then deletes the local instance of b and then displays the value of b. This produces what may be a surprising result if you missed my previous exploration of JavaScript objects.

The statement in the main code that calls oB.f() produces the following sequences of output messages:

Calling oB.f()
NewA.prototype.f()
NewB.prototype.f()
NewA.prototype.f()
NewB instance f() -- a=1, b=3
b = 2

.
Let's follow the trail here to see what happened.

oB.f() invoked the static method f() of NewB, since NewB did not define an instance version. The f()static method called this.inherited.f() which is the static f() method of NewA. This method reported the message "NewA.prototype.f()". Then the f() static method of NewB reported its message, "NewB.prototype.f()". Then, since this method was invoked through an instance of NewB, the this property refered to the instance, and thus had access to the f1() instance method, so f1() was called.

The first thing f1() does is, once again, call the static f() method of NewA, thus we see the next message reporting, "NewA.prototype.f()". Then control returns to f1() and the next message is displayed, "NewB instance f() -- a=1, b=3". The values of a and b are displayed, 1 and 3 respectively. This shows that the value of a was inherited from NewA, and that the value of b was defined by the NewB constructor.

Now an interesting thing happens. The b instance property is deleted and its value is then displayed. You might think that the value of b would be undefined at this point. However, the message "b = 2" proclaims otherwise. What has happened here is that instance oB no longer has an instance property b. However, since it inherited a value from NewA for b - in this case, 2 - then that value is still available to oB and thus appears. Now, if you were to assign a new value to oA.b (say, oA.b = 5) and then execute oB.f() again, the value of oB.b remains unchanged. So, this begs the question, where is this instance value stored?

Good question. I don't know.

So, now I wonder if there is any mechanism in JavaScript whereby one can access the inherited instance values? If so, I haven't found it, yet, and it's also kind of awkward to have to go through the steps I illustrated here to access inherited static properties and methods.

Other questions I hope to explore in JavaScript is how a function can be an object, too. For example, I can define function X() { alert("do something"); } and then turn right around and add a property to it, like X.a = 5; I can refer to X.a to get its value, or call X() to invoke the function. How can it represent code and structure at the same time?

Enquiring minds want to know.

In fact, if you really want some confusion, try something like this the next time you are poking around:

function X() { alert("a = "+X.a); }
X.a = 5;
X();

.
Stay tuned for more JavaScript mind benders...

posted by Bill  # 1:53 PM