ECMAScript 5 Part 1: Reusable Code

October 28th, 2010

In June, we wrote about IE9’s support for ECMAScript 5 (ES5) and published a TestDrive demo to explore some of the features. With IE9 Beta available, it’s a good time to talk about how developers can use ES5 to improve their code. Many of the features in ES5 are designed to help developers working with very large code bases by making the language both more robust and easier to maintain. In this series of blog posts we’ll look specifically at how ES5 helps you write more reusable code.

For this post, we’ll focus on how ES5 makes it easier to create and clone objects and helps you ensure that your code is robust to mistakes when it is reused – even if you’re the only developer who will be using it.

The example we’ll use is a simple set of calendar objects, similar to the kinds of objects you see in a web mail program like Hotmail or Outlook Web Access.

Creating Objects: new and Object.create

There’s a saying that the best code is the code you don’t have to write. ES5’s object constructor methods cut down on the amount of repetitious code quite a bit. As an example, let’s start by creating an object representing an appointment. The simplest way to create a JavaScript object with some initialized properties is with an object literal:

var appointment = { name: "unnamed appointment", startTime: new Date(NaN), endTime: new Date(NaN), location: "unknown"

That object can serve as a pattern for any other appointment objects we want to create, analogous to creating a class. Prior to ES5, if we wanted to create a prototype from this object we would define a constructor function and specify the prototype to be used:

function Appointment(){};
Appointment.prototype = appointment; var myAppointment = new Appointment();

(Note that the Appointment function doesn’t actually do anything in this case. It just provides the place to specify the value to use as the prototype for new objects.)

This pattern is familiar to JavaScript developers today. But with the object constructor methods in ES5, there’s a more direct way to create an object with a specific prototype:

var myAppointment = Object.create(appointment);

Any object created by the above call to Object.create will have the appointment object as its prototype and inherit all its properties; there’s no need to create a constructor function.

var myAppointment = Object.create(appointment); = "Dentist Appointment";
myAppointment.startTime = new Date("10/31/2010 08:00");

These assignments override the like-named inherited properties rather than actually modify the properties of the prototype object. The value of remains “unnamed appointment”; the value of unassigned inherited properties like location remains “unknown.”

Extend and Constrain

Now that we’ve created a base class pattern and have leveraged the prototype to create another instance using Object.create, let’s look at how ES5 helps you extend the base class to create another reusable class. Let’s start by creating a class which can serve as a meeting prototype. The second argument of create is a property descriptor which enables us to create and set the value of properties. We’ll set conferenceCall to a default value, just in case we use it in a later operation without setting the value.

var meeting = Object.create(appointment, { conferenceCall: {value: "In-person meeting" }

Alternatively, we could do this by calling the defineProperty method, which is also new to ES5.

var meeting = Object.create(appointment);
Object.defineProperty(meeting, "conferenceCall", { value: "In-person meeting" });

You might ask how the code examples above are different than the following code:

var meeting = Object.create(appointment);
meeting.conferenceCall = "In-person meeting"; 

The defineProperty method defaults the writable, enumerable, and configurable attributes of a new property to false. If we did not want to make the property read-only, not enumerable, and configurable then we would have used the simpler property initialization. The property definition parameters to Object.create also have the same defaults.

For a computed field, a property which is a function of other modifiable fields, we want to ensure that the property value can’t be modified directly. We can use the getter functionality in ES5 to specify the function which computes the value. (Actual function has been simplified for illustrative purposes.)

Object.defineProperty(meeting, "duration", { get: function () { return this.endTime.getHours() - this.startTime.getHours(); }}

We now create an instance of the meeting.

var teamMeeting = Object.create(meeting); = "IE Team Meeting";
teamMeeting.startTime = new Date("10/31/2010 08:00");
teamMeeting.endTime = new Date("10/31/2010 09:00");
teamMeeting.location = "Conference Room 33";

With the enumerable attribute of the properties created with defineProperty being false by default, the duration and conferenceCall properties are not enumerated when looping through the members of the object. The enumeration only contains those properties that are necessary to describe the object.

name: IE Team Meeting
startTime: Sun Oct 31 08:00:00 PDT 2010
endTime: Sun Oct 31 09:00:00 PDT 2010
location: Conference Room 33

With the configurable attribute of the property defaulting to false, users of the class cannot write over the duration computed property.

Send in the Clones

ES5 makes it easier to implement object cloning. Object cloning is mostly a feature you see in JavaScript frameworks like jQuery or Dojo. These frameworks have to do a lot of work to make sure that the right things happen when you clone an object. With previous versions of ECMAScript, it was really easy to write a naïve clone function which only copies the members they enumerate.

function naiveClone (obj) { var n={}; for (var p in obj) { n[p]=obj[p]; }; return n;

For some implementations of object cloning, when you just want to copy the data, you might end up with too many members including those that contain behavior. For other implementations of object cloning when you want a deep copy, you might end up with too few members. With the new Object methods of ES5, writing a good clone function is much easier.

Object.clone = function (o) { var n = Object.create(Object.getPrototypeOf(o)); var props = Object.getOwnPropertyNames(o); var pName; for (var p in props) { pName = props[p]; Object.defineProperty(n, pName, Object.getOwnPropertyDescriptor(o, pName)); }; return n;

The getOwnPropertyNames function only gets those properties that are unique to this object. So, for example, it doesn’t re-clone the inherited methods that exist on all Objects like toString and valueOf. Going back to our earlier example, using getOwnPropertyNames, we can recreate a series of team meetings over the next few months:

var quarterlyTeamMeeting = Object.clone(teamMeeting);
quarterlyTeamMeeting.startTime = new Date("12/31/2010 07:00");
quarterlyTeamMeeting.endTime = new Date("12/31/2010 09:00");

The duration property exists on this object and yet it does not show up in the enumeration. Of course, writing a robust, one-size-fits-all, clone method is not necessarily as simple as the code above; for example, you have to take into account special characteristics of some of the built-in objects such as Strings or Arrays. For this reason, the ECMAScript 5 committee left creating a cloning function to frameworks authors.

Getters and Setters

Getters and setters are among the most anticipated features in ES5. Suppose you’ve got a property which triggers an event whenever anyone sets its field. In this example, we’ll instead define the location property such that if the location is updated, we want to fire an event to allow subscribers to see if the location is available. To explore this, we’ll revisit the meeting declaration to create a new property which fires an event when set.

(function () { var loc = "undetermined location"; Object.defineProperty(meeting, "location", { get: function () { return loc; }, set: function (value) { loc = value; /*fire some event */ } });

In the example above, we wrapped the property declaration with a function to limit the scope of the variable loc to the property. This makes the variable serve as a backing store as opposed to an accessible field.

During IE8, while the ES5 specification was still in the making, we introduced accessor support for DOM objects. Now that the spec is ratified, we have added full support for this feature.

Moving the web forward with ES5

These are some of the ways you can use ES5 to create more robust code that can be more easily reused. Remember, to leverage these features, the browser must be in IE9 Standards Document Mode which is the default in IE9 for a standard document type.

As the web evolves, JavaScript continues to be used for larger applications. As usage patterns emerge, they are incorporated into browser implementations and web standards. Some of the principles the standards working group applied to ES5 to support these demands are:

  • No New Syntax. When there’s a syntax error, the page stops loading. Many of the constructs explored in this blog post which are new to ES5 are designed to be simple methods on the Object constructor, thereby leveraging method calling syntax.
  • Minimize construct collision. Introducing new members on Object could conflict with frameworks which extend Object’s prototype. Note that the functions used above are all extensions on the Object constructor (with a capitol “O”) which is a less commonly used design pattern.
  • Appropriate layering of abstractions. Meta-level methods that manipulate the structure and definition of objects are clearly separated from the “business logic” methods.

ES5 delivers on these principles and represents increased productivity for frameworks authors. We think these extensions, and others we’ll discuss in future posts, will enable developers to create richer standards-based experiences on the web. We’re excited to see how developers will use the features of ES5 with IE9; we’d like to hear how you plan to put these features to use. We’ll continue to explore the additions to ES5 in upcoming posts, stay tuned.

Amanda Silver
Program Manager