Creating JavaScript Properties in ASP.NET AJAX

JavaScript (or more accurately, ECMAScript 3.0) does not support properties. JavaScript objects do not have properties in the same sense as C# or VB.NET objects have properties. That’s unfortunate, since there are benefits to C# and VB.NET properties:

· You can use a property as a public interface to a private field. Typically, you want to keep all of an object’s fields private and only expose certain fields through properties.

· You can use a property to perform logic when reading or writing to a field. For example, you can perform validation in your setter to make sure that a new value for a property is the right type of value.

If you want to work with properties in JavaScript, then you must do something special. In this blog entry, I’m going to talk about different methods for getting property-like behavior out of JavaScript. I’m going to start by providing a quick review of how objects, properties, and methods work in JavaScript. Next, I’m going to talk about property support in the Firefox browser. Finally, I’m going to talk about two methods of simulating properties in JavaScript: using closures and using the Intellisense feature of Visual Studio 2008.

JavaScript Objects, Properties, and Methods

People talk about JavaScript properties all the time. If JavaScript properties don’t really exist, then what are they talking about? Let me provide some background on JavaScript objects, properties, and methods.

JavaScript is an extremely simple language. A JavaScript object is nothing more than a collection of name and value pairs. I tend to think of a JavaScript object as a Hashtable.

A JavaScript property is simply an item in a JavaScript object. There are multiple ways that you can set a JavaScript property. For example, the following statements create a new JavaScript object with a new property named prop1 that has the value “Hello”:

   1:  var thing = new Object();
   2:   
   3:  thing.prop1 = “Hello”;
   4:   

Since a JavaScript object is simply a Hashtable, you also can write the statements this way:

   1:  var thing = new Object();
   2:   
   3:  thing[ “prop1” ] = “Hello”;
   4:   

Finally, you can use object literal syntax to create a new object with a property in a single line like this:

   1:  var thing = {prop1 : “Hello”};

(The statement above should remind you of the new object initializer syntax in C# 3.0. Yes, JavaScript has had this feature since the last century.)

You can use either dot notation or collection notation to read a property:

   1:  var thing = new Object();
   2:   
   3:  thing.prop1 = “Hello”;
   4:   
   5:  alert( thing[“prop1”] ); // displays Hello
   6:   
   7:  alert( thing.prop1 ); // displays Hello
   8:   

Normally, when people talk about JavaScript properties, they mean properties in the sense just described. They mean items in a Hashtable that represent some value.

A JavaScript method is just a JavaScript property with a particular type of value. A JavaScript method is a JavaScript property that represents a function. For example, you can use the following code to create a JavaScript object that displays a random quotation:

   1:  <script type="text/javascript">
   2:   
   3:  var quoter = new Object();
   4:   
   5:  quoter.sayQuote = function()
   6:   
   7:  {
   8:   
   9:    switch (Math.floor(Math.random() * 3)) 
  10:   
  11:    {
  12:   
  13:      case 0: return "Man is the measure of all things.";
  14:   
  15:      case 1: return "The early bird gets the worm.";
  16:   
  17:      case 2: return "Look before you leap!";
  18:   
  19:    }
  20:   
  21:  };
  22:   
  23:  document.write( quoter.sayQuote() );
  24:   
  25:  </script>
  26:   

In the script above, a new object named quoter is created. A property named sayQuote is added to the quoter object. This property represents a function that returns a random quotation. Since the sayQuote property represents a function, the sayQuote property is a method.

You can rewrite the script above to use object literal syntax:

   1:  <script type="text/javascript">
   2:   
   3:  var quoter = 
   4:   
   5:  {
   6:   
   7:    sayQuote: function()
   8:   
   9:    {
  10:   
  11:      switch (Math.floor(Math.random() * 3)) 
  12:   
  13:      {
  14:   
  15:        case 0: return "Man is the measure of all things.";
  16:   
  17:        case 1: return "The early bird gets the worm.";
  18:   
  19:        case 2: return "Look before you leap!";
  20:   
  21:      }
  22:   
  23:    }
  24:   
  25:  };
  26:   
  27:  document.write( quoter.sayQuote() );
  28:   
  29:  </script>
  30:   

This new script does the exact same thing as the previous script. Notice, however, that the sayQuote method is defined when the object is initialized.

There is one more way of creating properties and methods that I want to discuss before leaving this section. You can create object properties and methods by using a constructor function. What’s a constructor function?

JavaScript is not a class based language like C# or VB.NET. In the current version of JavaScript, there is no way to define a class. When working with JavaScript, if you want to make a bunch of things of a certain type, you create the things using a constructor function.

You can think of a constructor function as an object factory. It takes a new object and stamps properties and methods on the new object. For example, the following code creates a constructor function named Widget. Two widget objects are created with the Widget constructor:

   1:  <script type="text/javascript">
   2:   
   3:  function Widget()
   4:   
   5:  {
   6:   
   7:    this.dateCreated = new Date();
   8:   
   9:    this.reportCreated = function()
  10:   
  11:    {
  12:   
  13:      alert(this.dateCreated);
  14:   
  15:    };
  16:   
  17:  }
  18:   
  19:  var widget1 = new Widget();
  20:   
  21:  var widget2 = new Widget();
  22:   
  23:  widget1.reportCreated();
  24:   
  25:  widget2.reportCreated();
  26:   
  27:  </script>

A new widget is created by calling new Widget(). The new operator creates a new object and passes the new object to the Widget() function. Within the Widget() function, the keyword this refers to the new object created by the new operator. The function adds two properties to the object: one property that represents the date and time the widget was created and one property that represents a function for retrieving the date created.

JavaScript Properties in Firefox

Unfortunately for JavaScript developers, different browsers have different JavaScript engines. Internet Explorer uses the JScript engine and Firefox uses the SpiderMonkey engine. SpiderMonkey, unlike JScript, does support properties in the same sense as C# or VB.NET. The implementation of properties in FireFox, however, is outside of the ECMAScript standard.

There are two ways to create properties when working with Firefox. First, you can use object literal syntax that looks like this:

   1:  <script type="text/javascript">
   2:   
   3:  var product = 
   4:   
   5:  {
   6:   
   7:    _price: 0,
   8:   
   9:    get price() { return this._price; },
  10:   
  11:    set price(value)
  12:   
  13:    {
  14:   
  15:      if (value < 0)
  16:   
  17:        throw "price must be greater than 0";
  18:   
  19:      this._price = value;
  20:   
  21:    }
  22:   
  23:  };
  24:   
  25:  // set the price using setter
  26:   
  27:  product.price = 2.22;
  28:   
  29:  // get the price using getter
  30:   
  31:  alert( product.price );
  32:   
  33:  // set the price to illegal value
  34:   
  35:  product.price = -4.44;
  36:   
  37:  </script>

Entering the code above into Visual Studio 2008 is a little bit of a struggle since Visual Studio does not recognize the get and set keywords (the little bit of struggle here is a small price to pay for the Intellisense everywhere else).

This code defines a new object named product. This object has a field named _price. Furthermore, both a getter and setter for a property named price are defined. The getter is defined by using the get keyword and the setter is defined by using the set keyword.

If you attempt to assign a value less than 0 to the price property, the price setter throws an exception. The setter allows us to perform logic when modifying a property.

An alternative and uglier syntax for defining getters and setters is to use the magic __defineGetter__ and __defineSetter__ methods. The following code does the same thing as the previous code:

   1:  <script type="text/javascript">
   2:   
   3:  var product = 
   4:   
   5:  {
   6:   
   7:    _price: 0
   8:   
   9:  };
  10:   
  11:  product.__defineGetter__("price", function(){ return this._price; });
  12:   
  13:  product.__defineSetter__("price", function(value)
  14:   
  15:    {
  16:   
  17:      if (value < 0)
  18:   
  19:        throw "price must be greater than 0";
  20:   
  21:      this._price = value;
  22:   
  23:    });
  24:   
  25:  // set the price using setter
  26:   
  27:  product.price = 2.22;
  28:   
  29:  // get the price using getter
  30:   
  31:  alert( product.price );
  32:   
  33:  // set the price to illegal value
  34:   
  35:  product.price = -4.44;
  36:   
  37:  </script>

This last code is hideously ugly, but it works. The __defineGetter__ method is used to define a getter on an existing object and the __defineSetter__ method is used to define a setter on an existing object.

There are two major problems with using the approach to creating properties discussed in this section. First, it only works with Firefox and not with other browsers such as Internet Explorer or Opera. Second, this approach to creating properties only provides us with one of the two benefits of properties discussed in the introduction to this blog entry. This approach enables us to execute logic when accessing a property. However, it does not enable us to create private fields that can only be accessed by way of a property. In the two previous samples, the field _price is still accessible outside of the product object: product._price works just fine. We really want this field to be private.

Creating Properties with Closures

The most popular method for creating properties is the Douglas Crockford method (http://javascript.crockford.com/private.html). The Crockford method provides you with both of the traditional benefits of properties. When using the Crockford method, you can make certain object fields private in such a way that the fields can only be accessed through properties. Furthermore, when using the Crockford method, you can perform logic whenever a property is read or set.

The biggest benefit of the Crockford method, however, is that this method works with all browsers. You can use the Crockford method with Internet Explorer, Firefox, Opera, Safari, or any other browser that supports basic JavaScript.

The following code sample uses the Crockford method to create an object named product that has a private field named _price that can only be accessed through a public property named price:

   1:  <script type="text/javascript">
   2:   
   3:  function Product()
   4:   
   5:  {
   6:   
   7:    var _price = 0;
   8:   
   9:    this.get_price = function(){ return _price; };
  10:   
  11:    this.set_price = function(value)
  12:   
  13:    {
  14:   
  15:      if (value < 0)
  16:   
  17:        throw "price must be greater than 0";
  18:   
  19:      _price = value;
  20:   
  21:    };
  22:   
  23:  }
  24:   
  25:  var product = new Product();
  26:   
  27:  // attempt to read _price field
  28:   
  29:  alert(product._price);
  30:   
  31:  // set the price using setter
  32:   
  33:  product.set_price(2.22);
  34:   
  35:  // get the price using getter
  36:   
  37:  alert( product.get_price() );
  38:   
  39:  // set the price to illegal value
  40:   
  41:  product.set_price( -4.44 );
  42:   
  43:  </script>

The code above contains a constructor function named Product. The constructor function adds two methods to any object that the constructor function products: a method named get_price and set_price. Both of these methods are public methods.

Notice that get_price and set_price refer to a variable named _price. The _price variable is private. You cannot access the value of _price by using the syntax product._price. If you call product._price then you get the value undefined.

The trick here is that the Product constructor function uses a closure. In JavaScript, a scope chain is created when a function is created (and not when it is executed). The variable _price becomes captured in the scope chains for the get_price and set_price functions. Therefore, you can refer to _price within get_price and set_price, but not outside of these functions.

The Crockford method is a good approach for creating properties. In the original version of the Microsoft AJAX Framework, you were encouraged to use this approach for creating properties. However, this approach was abandoned with later versions of ASP.NET AJAX for the approach discussed in the next section.

Properties by Convention in ASP.NET AJAX

ASP.NET AJAX takes a tools approach rather than a language approach to creating properties. Certain properties won’t show up in Visual Studio 2008 Intellisense. I did some experimenting and deduced the following sets of rules:

When Referring to an Object Defined in the Same File:

· All JavaScript properties appear in Intellisense

When Referring to an Object Defined in another File from a JavaScript File:

· Any JavaScript property defined in a constructor function won’t appear in Intellisense

When Referring to an Object Defined in another File from a Page:

· Any JavaScript property defined in a constructor function won’t appear in Intellisense

· Any JavaScript property that begins with an underscore character won’t appear in Intellisense

These rules define three different access levels for JavaScript properties. A property defined outside of a constructor and without an underscore is publically accessible. The property appears in Intellisense in other JavaScript files and pages.

A property defined outside of a constructor with an underscore is library accessible. The property will appear in Intellisense within any JavaScript file that refers to the file containing the object. However, a Library accessible property will not appear within a page.

Finally, a property defined within the constructor function is privately accessible. The property won’t appear in Intellisense in any page or JavaScript file apart from the page/file in which the object is defined.

So, let’s look at a quick sample. The following JavaScript file, named Product.js, defines a product:

   1:  function Product()
   2:   
   3:  {
   4:   
   5:    this._price = 0; // private
   6:   
   7:    this.name = "???"; // private
   8:   
   9:  }
  10:   
  11:  Product.prototype = 
  12:   
  13:  {
  14:   
  15:    _salePrice: 2.99, // library
  16:   
  17:    category: "Electronics", // public
  18:   
  19:    get_price: function() {return this._price}, // public
  20:   
  21:    set_price: function() // public
  22:   
  23:    {
  24:   
  25:      if (value < 0)
  26:   
  27:        throw "price must be greater than 0";
  28:   
  29:      this._price = value;
  30:   
  31:    },
  32:   
  33:    _displayName: function() {alert(this.name)} // library 
  34:   
  35:  }
  36:   

I’ve added comments to the different members of the Product class to indicate whether the member is private, library, or public accessible.

Within the file above, Product.js, Intellisense works for any property of the Product object:

AllProps

The properties that appear in Intellisense change when you refer to the object from another JavaScript file. For example, the following JavaScript file refers to the Product.js file.

   1:  /// <reference path="Product.js"/>
   2:   
   3:  var product2 = new Product();
   4:   

Notice the JavaScript comment at the top of this file. This comment causes Visual Studio 2008 to refer to add Intellisense for the Product.js file when working in this file. If you type product2, the following Intellisense appears:

LibraryProps

Finally, another set of properties appears within Intellisense when you refer to the Product class within a page. The following page uses the ASP.NET ScriptManager control to refer to the Product.js page:

   1:  <%@ Page Language="C#" %>
   2:   
   3:  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
   4:   
   5:  <html xmlns="http://www.w3.org/1999/xhtml">
   6:   
   7:  <head runat="server">
   8:   
   9:    <title>Show Product</title>
  10:   
  11:  </head>
  12:   
  13:  <body>
  14:   
  15:  <form id="form1" runat="server">
  16:   
  17:  <div>
  18:   
  19:    <asp:ScriptManager ID="sm1" runat="server">
  20:   
  21:    <Scripts>
  22:   
  23:      <asp:ScriptReference Path="~/JProps/Product.js" />
  24:   
  25:    </Scripts>
  26:   
  27:    </asp:ScriptManager>
  28:   
  29:    <script type="text/javascript">
  30:   
  31:      var product1 = new Product();
  32:   
  33:    </script>
  34:   
  35:  </div>
  36:   
  37:  </form>
  38:   
  39:  </body>
  40:   
  41:  </html>
  42:   

When you type product1 within the page above, you get the following Intellisense:

PageProps

This approach is called creating properties by convention since the privacy of a field is not enforced at the language level. Instead, the privacy of a field is suggested at the tool level since Visual Studio 2008 hides the field from you.

In this approach, a closure is no longer used for the private _price field. In fact, there is nothing to prevent you from reading the value of the private _price field from any file or page by using product._price.

What Method is Best?

So, which approach should you use when creating JavaScript properties? We have discussed three approaches: use the Firefox proprietary getters and setters method, use the Crockford method, or use the Visual Studio 2008 properties by convention method.

Clearly, the Firefox proprietary getter and setter approach is not an option because it does not work with Internet Explorer (or Opera). Even if this approach worked in all browsers, it would still not be a very good approach. The Firefox getter and setter approach does not provide you with a method of creating private fields.

The second approach, the Crockford approach, appears to be the best approach. This approach works in all browsers and it provides you with a way to create genuine private fields. So, why aren’t you encouraged to use this approach when working with the ASP.NET AJAX Framework?

Here’s what a member of the ASP.NET AJAX team has to say on this matter (http://weblogs.asp.net/bleroy/archive/2006/10/11/From-closures-to-prototypes_2C00_-part-1.aspx). Bertrand Le Roy writes:

But there's another problem with closure-based classes and that's performance. For every instance you create, all the methods have to be recreated. This means that you pay a higher price to object creation and that the working set gets bigger with each instance.

This is an objection to the Crockford method. The Crockford method is bad, according to Bertrand Le Roy, because it results in wasted memory. Let me explain the reasoning here.

When you create a constructor function, the function acts as an object factory. Whenever you create a new instance of an object using the constructor function, all of the properties and methods created in the constructor function are stamped onto the new object. For example, the following constructor function can be used to create new Product objects:

   1:  <script type="text/javascript">
   2:   
   3:  function Product()
   4:   
   5:  {
   6:   
   7:    var _price = 0;
   8:   
   9:    this.get_price = function(){ return _price; };
  10:   
  11:    this.set_price = function(value)
  12:   
  13:    {
  14:   
  15:      if (value < 0)
  16:   
  17:        throw "price must be greater than 0";
  18:   
  19:      _price = value;
  20:   
  21:    };
  22:   
  23:  }
  24:   
  25:  var product1 = new Product();
  26:   
  27:  var product2 = new Product();
  28:   
  29:  </script>

The script above creates two Product objects named product1 and product2. Here’s Bertrand’s objection. Each time the Product() constructor function is called, the get_price() and set_price() methods must be added to the new object instance. The product1 and product2 objects both get get_product() and set_product() methods stamped onto them. This seems really wasteful.

A better approach, Bertrand suggests, is to use an object prototype. That way, every instance of a Product will share the exact same reference to get_product() and set_product(). However, prototypes do not work with closures. The following script does not do what you expect:

   1:  <script type="text/javascript">
   2:   
   3:  function Product()
   4:   
   5:  {
   6:   
   7:    var _price = 0;
   8:   
   9:  }
  10:   
  11:  Product.prototype = 
  12:   
  13:    {
  14:   
  15:      get_price: function(){ return _price; },
  16:   
  17:      set_price: function(value)
  18:   
  19:      {
  20:   
  21:        if (value < 0)
  22:   
  23:          throw "price must be greater than 0";
  24:   
  25:        _price = value;
  26:   
  27:      }
  28:   
  29:  };
  30:   
  31:  var product1 = new Product();
  32:   
  33:  product1.set_price( 1.22 );
  34:   
  35:  var product2 = new Product();
  36:   
  37:  product1.set_price( 4.55 );
  38:   
  39:  alert( product1.get_price() ); // displays 4.55
  40:   
  41:  </script>

The problem with the script above is that the _price variable in the get_price() and set_price() methods no longer refers to the _price variable in the Product constructor. Instead, the _price variable ends up being a global variable. This is not what you want. The final statement of the script displays the value 4.55 for product1 instead of the intended value of 1.22.

Therefore, since you should use prototypes to save memory, and closures don’t work with prototypes, you should avoid closures when creating properties. Here, once again, is the proper way of creating JavaScript objects using ASP.NET AJAX:

   1:  <script type="text/javascript">
   2:   
   3:  function Product()
   4:   
   5:  {
   6:   
   7:    this._price = 0;
   8:   
   9:  }
  10:   
  11:  Product.prototype = 
  12:   
  13:    {
  14:   
  15:      get_price: function(){ return this._price; },
  16:   
  17:      set_price: function(value)
  18:   
  19:      {
  20:   
  21:        if (value < 0)
  22:   
  23:          throw "price must be greater than 0";
  24:   
  25:        this._price = value;
  26:   
  27:      }
  28:   
  29:  };
  30:   
  31:   
  41:  </script>

Now, when I first read this argument, I did not find it very convincing. I assumed that the memory difference would be, at best, very minor. I assumed that each product object would refer to the exact same get_price() and set_price() methods. In other words, I thought that these functions would be referred to by reference instead of by value. It turns out that I was wrong as can be seen by the following script:

   1:  <script type="text/javascript">
   2:   
   3:  // Constructor functions are different
   4:   
   5:  function Widget()
   6:   
   7:  {
   8:   
   9:    this.doSomething = function() {alert ('boom');};
  10:   
  11:  }
  12:   
  13:  var widget1 = new Widget();
  14:   
  15:  var widget2 = new Widget();
  16:   
  17:  alert(widget1.doSomething === widget2.doSomething); // returns false
  18:   
  19:  // Prototype functions are the same
  20:   
  21:  function Thing()
  22:   
  23:  {
  24:   
  25:  }
  26:   
  27:  Thing.prototype =
  28:   
  29:    {
  30:   
  31:      doSomething: function() {alert ('boom');}
  32:   
  33:    }
  34:   
  35:  var thing1 = new Thing();
  36:   
  37:  var thing2 = new Thing();
  38:   
  39:  alert(thing1.doSomething === thing2.doSomething); // returns true
  40:   
  41:  </script>

The script above contains two constructor functions: a Widget and a Thing constructor function. The Widget constructor function adds a doSomething method to an object directly. However, each object created by this constructor function ends up with a duplicate doSomething method. The alert statement that compares the value of widget1.doSomething and widget2.doSomething returns the value false.

The Thing constructor function does not stamp each object with a duplicate doSomething method. Instead, the doSomething method is added to the Thing constructor function’s prototype. Each object constructed by the Thing constructor function shares the same doSomething method. The alert statement that compares the value of thing1.doSomething and thing2.doSomething returns the value true.

Conclusion

The approach taken within the ASP.NET AJAX framework makes sense. If you adopt the convention that field and methods that are defined in a constructor function or that start with an underscore are private, then you have an easy to understand convention for distinguishing private from public members of an object. Better yet, if you are using Visual Studio 2008, since Visual Studio 2008 will hide these property and method names automatically, you can let Intellisense be your guide.

It is worth emphasizing that you can use the Crockford method of creating properties even within the Microsoft AJAX Framework. It is not the pattern that the ASP.NET AJAX Framework objects use. However, there is nothing to prevent you from adding Crockford style properties to ASP.NET AJAX when you really, really need a field or method to be private down to the language level.

8 Comments

Comments have been disabled for this content.