ASP.NET AJAX In-Depth: Object Inheritance

The goal of the blog entry is simple: I want to understand everything happening under the covers when you take advantage of ASP.NET AJAX inheritance. So, let’s start with a simple code sample:

Listing 1 – AjaxInheritance.aspx

   1:  <script type="text/javascript">
   2:   
   3:  function BaseControl()
   4:  {
   5:      this._propA = "BaseControl propA";
   6:      this._propB = "BaseControl propB";
   7:  }
   8:   
   9:  BaseControl.prototype =
  10:  {
  11:      get_propA : function() {return this._propA;},
  12:      set_propA : function(value) {this._propA = value; },
  13:   
  14:      get_propB : function() {return this._propB; },
  15:      set_propB : function(value) {this._propB = value; }
  16:  }
  17:   
  18:  BaseControl.registerClass("BaseControl");
  19:   
  20:   
  21:  function TreeViewControl()
  22:  {
  23:      TreeViewControl.initializeBase(this);
  24:      this._propA = "TreeViewControl propA";
  25:  }
  26:   
  27:  TreeViewControl.prototype =
  28:  {
  29:      get_propA : function() {return this._propA;},
  30:      set_propA : function(value) {this._propA = value; }
  31:  }
  32:   
  33:  TreeViewControl.registerClass("TreeViewControl", BaseControl);
  34:   
  35:  var treeView1 = new TreeViewControl();
  36:   
  37:  alert( treeView1.get_propA() ); // displays "TreeViewControl propA"
  38:  alert( treeView1.get_propB() ); // displays "BaseControl propB"
  39:   
  40:      
  41:  </script>

The JavaScript code in Listing 1 defines two objects named BaseControl and TreeViewControl. These objects might represent user interface controls that are displayed in a web page. The TreeViewControl inherits from the BaseControl object.

First, the BaseControl object is defined with a constructor function named BaseControl. The constructor function initializes two fields named _propA and _propB. Next, get and set accessor methods are added to the BaseControl object’s prototype. These methods expose the private _propA and _propB fields as public properties. Finally, the BaseControl object is registered as a class by calling the ASP.NET AJAX registerClass() method. We’ll be discussing what this method call does in detail in a moment.

Second, a new object named TreeViewControl is defined. Notice that the TreeViewControl constructor function includes a call to an ASP.NET AJAX method named initializeBase(). We also need to discuss what is going on with this method in a moment.

Like the BaseControl, the TreeViewControl is registered as a class by calling the registerClass() method. However, the TreeViewControl class is registered as inheriting from the BaseControl class. The second parameter passed to the registerClass() method enables you to specify a base class.

Finally, an instance of the TreeViewControl is created named treeView1. The get_propA() and get_propB() methods of the object are called and the results of these method calls are displayed with alert boxes in the browser.

Understanding the registerClass() Method

The first ASP.NET AJAX method that we need to examine in detail is the registerClass() method. This method accepts the following three parameters:

· typeName – The name of the class being registered

· baseType – (optional) The base class for the class being registered

· interfaceTypes – (optional) The array of interfaces that the class being registered implements

You can view the entire source code for the registerClass() method by examining the MicrosoftAjax.debug.js file included in the standalone Microsoft AJAX Library. You can download the standalone library from http://msdn.microsoft.com/en-us/asp.net/bb944808.aspx.

I’ve stripped the validation code and the code related to working with interfaces from the registerClass() method and I’ve included the remaining source code in Listing 2.

Listing 2 – MicrosoftAjax.debug.js registerClass() method

   1:  Type.prototype.registerClass = function(typeName, baseType, interfaceTypes) {
   2:   
   3:      var parsedName;
   4:      try {
   5:          parsedName = eval(typeName);
   6:      }
   7:      catch(e) {
   8:          throw Error.argument('typeName', Sys.Res.argumentTypeName);
   9:      }
  10:      if (parsedName !== this) throw Error.argument('typeName', Sys.Res.badTypeName);
  11:      if (Sys.__registeredTypes[typeName]) throw Error.invalidOperation(String.format(Sys.Res.typeRegisteredTwice, typeName));
  12:      if (baseType && !baseType.__class) throw Error.argument('baseType', Sys.Res.baseNotAClass);
  13:      this.prototype.constructor = this;
  14:      this.__typeName = typeName;
  15:      this.__class = true;
  16:      if (baseType) {
  17:          this.__baseType = baseType;
  18:          this.__basePrototypePending = true;
  19:      }
  20:      Sys.__upperCaseTypes[typeName.toUpperCase()] = this;
  21:   
  22:      Sys.__registeredTypes[typeName] = true;
  23:      return this;
  24:  }

The first thing that you should notice about the code in Listing 2 is that the registerClass() method is a method of the Type object (it is defined on the Type prototype). What is the Type object? The Type object is an alias for the JavaScript Function object. The Microsoft AJAX Library creates this alias with the following line of code:

window.Type = Function;

Therefore, you can call the registerClass() method on any function. Typically, you would call registerClass() method on a function that acts as an object constructor.

Let’s go through the source code for the registerClass() method in Listing 2 step-by-step starting with the following code:

   1:  var parsedName;
   2:      try {
   3:          parsedName = eval(typeName);
   4:      }
   5:      catch(e) {
   6:          throw Error.argument('typeName', Sys.Res.argumentTypeName);
   7:      }
   8:      if (parsedName !== this) throw Error.argument('typeName', Sys.Res.badTypeName);

When you call the registerClass() method, the first parameter is a string that corresponds to the current function’s name. You call the registerClass() method for a class named TreeViewControl like this:

TreeViewControl.registerClass(“TreeViewControl”);

This seems redundant. Why do you need to refer to TreeViewControl twice: once as an object and once as a string? I assume that the goal was to get a string representation of the function name. The code above checks whether evaluating the string name equals the actual function. One way to avoid this redundancy would be to extract the name of the function by calling toString() on the function and matching the function name like this:

this.toString().match(/( \w+)/)[0] 

This line of code returns the name of the object that the current method is being called upon. Presumably, this approach was not used for performance reasons.

The next block of code for the registerClass() method looks like this:

   1: if (Sys.__registeredTypes[typeName]) throw Error.invalidOperation(String.format(Sys.Res.typeRegisteredTwice, typeName));
   2: if (baseType && !baseType.__class) throw Error.argument('baseType', Sys.Res.baseNotAClass);

The first line prevents you from registering two classes with the same class name. The second line validates whether or not the baseType parameter represents a class.

The next line of code looks like this:

    this.prototype.constructor = this;

This line of code is interesting. It fixes a problem with the constructor property in JavaScript illustrated with the code in Listing 3:

Listing 3 – BadConstructor.js

   1:      <script type="text/javascript">
   2:   
   3:      // Bad Constructor
   4:      function A() {}
   5:      function B() {}
   6:      
   7:      B.prototype = new A();
   8:      
   9:      var x = new B();
  10:      alert( x.constructor ); // Returns A
  11:   
  12:   
  13:      // Good Constructor
  14:      function A() {}
  15:      function B() {}
  16:      
  17:      B.prototype = new A();
  18:      B.prototype.constructor = B; // fix constructor
  19:      
  20:      var x = new B();
  21:      alert( x.constructor ); // Returns B
  22:   
  23:      
  24:      </script>

The constructor function should return the name of the function used to construct the current object. Unfortunately, when there is a prototype chain, the constructor property returns the wrong constructor function. It returns the name of the function used to construct the top object in the prototype chain.

When x.constructor is first called in Listing 3, the property returns the wrong value. When the x.constructor method is called later in the code (after fixing the constructor property), the x.constructor property returns the correct value.

Here’s the next block of code from the registerClass() method:

   1:      this.__typeName = typeName;
   2:      this.__class = true;
   3:      if (baseType) {
   4:          this.__baseType = baseType;
   5:          this.__basePrototypePending = true;
   6:      }
   7:      Sys.__upperCaseTypes[typeName.toUpperCase()] = this;
   8:   
   9:      Sys.__registeredTypes[typeName] = true;

This code adds several properties to the current constructor function: __typeName, __class, __baseType, and __basePrototypePending. These properties are used by the reflection methods and by the initializeBase() method that we discuss in the next section.

Finally, notice that the name of the constructor function (the class) is registered in two arrays kept in the Sys namespace: __upperCaseTypes and __registeredTypes. The __upperCaseTypes array is used by the Type.parse() method which takes a type name and returns an instance of the type. The __registeredTypes array is used by the registerClass() method to make sure that a class is not registered more than once.

One thing that you should notice about the registerClass() method is that it does not modify the prototype property. The constructor function’s prototype property is not modified until the initializeBase() method is called. We discuss initializeBase() in the next section.

Understanding the initializeBase() Method

You call the initializeBase() method in the constructor of a derived class. For example, in Listing 1, we called the initializeBase() method in the constructor function for the TreeViewControl class. If you don’t call this method, the derived class does not inherit anything from its super class.

I’ve included a stripped down version of the initializeBase() method in Listing 4.

Listing 4 – MicrosoftAjax.debug.js initializeBase()

   1:  Type.prototype.initializeBase = function(instance, baseArguments) {
   2:      if (!this.isInstanceOfType(instance)) throw Error.argumentType('instance', Object.getType(instance), this);
   3:      this.resolveInheritance();
   4:      if (this.__baseType) {
   5:          if (!baseArguments) {
   6:              this.__baseType.apply(instance);
   7:          }
   8:          else {
   9:              this.__baseType.apply(instance, baseArguments);
  10:          }
  11:      }
  12:      return instance;
  13:  }

Like the registerClass() method, the initializeBase() method is created as a method of the Function object (The Function object is aliased with the name Type). So you can call initializeBase() on any function. The intention is that you will call this method within a constructor function on the current function (or a super class of the current function).

Let’s go through the code in Listing 4 line-by-line starting with the following code:

if (!this.isInstanceOfType(instance)) throw Error.argumentType('instance', Object.getType(instance), this);

This code validates whether the instance parameter passed to the initializeBase() method corresponds to the current function (or a super class of the current function). For example, the following code will throw an exception since the initializeBase() method is being called on the A function within the C function (You’ll only get these exceptions when in debug mode):

Listing 5 – BadInitializeBase.js

   1:  <script type="text/javascript">
   2:   
   3:  function A() {}
   4:  A.registerClass("A");
   5:  function B() {}
   6:  B.registerClass("B");
   7:  function C()
   8:  {
   9:      A.initializeBase(this); // throws exception
  10:      B.initializeBase(this); // ok
  11:      C.initializeBase(this); // ok
  12:  }    
  13:  C.registerClass("C", B);
  14:   
  15:  var x = new C();
  16:   
  17:   
  18:  </script>

The next statement in the initializeBase() method is the most important statement:

    this.resolveInheritance();

This statement modifies the current function's prototype by calling the resolveInheritance() method. The resolveInheritance() method (slightly edited) is contained in Listing 6.

Listing 6 – MicrosoftAjax.debug.js resolveInheritance()

   1:  Type.prototype.resolveInheritance = function() {
   2:      if (this.__basePrototypePending) {
   3:          var baseType = this.__baseType;
   4:          baseType.resolveInheritance();
   5:          for (var memberName in baseType.prototype) {
   6:              var memberValue = baseType.prototype[memberName];
   7:              if (!this.prototype[memberName]) {
   8:                  this.prototype[memberName] = memberValue;
   9:              }
  10:          }
  11:          delete this.__basePrototypePending;
  12:      }
  13:  }

The resolveInheritance() method checks whether or not the constructor function’s __basePrototypePending property has the value true. The registerClass() method sets __basePrototypePending to the value true when you register a new class.

Next, the resolveInheritance() method is called recursively for each constructor function in the inheritance chain. When the top of the chain is hit, all of the properties of the prototype of a constructor function higher in the chain are copied down.

Consider the code in Listing 7 (the same code as Listing 1).

Listing 7 – AjaxInheritance.js

   1: <script type="text/javascript">

   2:   
   3:  function BaseControl()
   4:  {
   5:      this._propA = "BaseControl propA";
   6:      this._propB = "BaseControl propB";
   7:  }
   8:   
   9:  BaseControl.prototype =
  10:  {
  11:      get_propA : function() {return this._propA;},
  12:      set_propA : function(value) {this._propA = value; },
  13:   
  14:      get_propB : function() {return this._propB; },
  15:      set_propB : function(value) {this._propB = value; }
  16:  }
  17:   
  18:  BaseControl.registerClass("BaseControl");
  19:   
  20:   
  21:  function TreeViewControl()
  22:  {
  23:      TreeViewControl.initializeBase(this);
  24:      this._propA = "TreeViewControl propA";
  25:  }
  26:   
  27:  TreeViewControl.prototype =
  28:  {
  29:      get_propA : function() {return this._propA;},
  30:      set_propA : function(value) {this._propA = value; }
  31:  }
  32:   
  33:  TreeViewControl.registerClass("TreeViewControl", BaseControl);
  34:   
  35:  var treeView1 = new TreeViewControl();
  36:   
  37:  alert( treeView1.get_propA() ); // displays "TreeViewControl propA"
  38:  alert( treeView1.get_propB() ); // displays "BaseControl propB"
  39:   
  40:      
  41:  </script>

When initializeBase() is called in the constructor for the TreeView control, the resolveInheritance() method is called. The resolveInheritance() method looks up the base type for the TreeViewControl constructor function. In this case, the base type is BaseControl. Each property from the base type’s prototype is copied to the TreeViewControl constructor’s prototype. In particular, the get_propA, set_propA, get_propB, set_propB, and constructor properties are copied from the prototype for BaseControl to the prototype for TreeViewControl.

Why are the properties copied from the parent constructor function’s prototype to the current prototype? In other words, why are the prototypes flattened? Presumably, for reasons of performance. If all of the properties are copied to the lowest prototype in the prototype chain, then the prototype chain never needs to be climbed to resolve the value of any property.

In Firefox, the following statement returns the value true:

alert( treeView1.__proto__.hasOwnProperty("get_propB") ); // displays true

The __proto__ property is a Firefox only property that represents the current object’s prototype. The hasOwnProperty() JavaScript method returns true only when a property is a local property of an object and not when a property is read from the prototype chain. This statement shows that the prototype has been flattened and the prototype chain can be ignored when reading a property.

The final code executed by the initializeBase() method appears below:

   1:      if (this.__baseType) {
   2:          if (!baseArguments) {
   3:              this.__baseType.apply(instance);
   4:          }
   5:          else {
   6:              this.__baseType.apply(instance, baseArguments);
   7:          }
   8:      }

This code checks whether the current constructor function has been assigned a base type in the registerClass() method. If there is a base type, the JavaScript apply() method is called to pass the object being created to the constructor function for the base type. This last bit of code is necessary to initialize any private fields from the base class for the current object. The apply() method explains how the treeView1 object gets its _propA and _propB fields.

Conclusion

While writing this blog entry and working through the registerClass() and initializeBase() methods, I was surprised to discover that the ASP.NET AJAX Framework flattened the prototype chain. I was under the impression that ASP.NET AJAX used strict JavaScript prototype inheritance. Presumably, as mentioned previously, the prototype chain is flattened for performance reasons.

There is a lot of interesting code in the Microsoft AJAX Library. There’s a lot that can be learned from a close study of the source code.

1 Comment

  • Impressive analysis. A few more things to point out. First, the inheritance resolving only happens the first time the class is instantiated. This guarantess fast library loading, and that you only pay the price for inheritance when you actually use it. Second, a great deal of what you see in the debug script is not in the release version. For example, evaluating the class name to check it against the actual constructor is something you don't want to do in release mode because it's so expensive. Lots of interface-related code is also absent from the release version.

Comments have been disabled for this content.