ECMAScript 5: Object creation and property definition

The newest version of JavaScript, ECMAScript 5, is now available in the latest browsers, and brings with it a slew of new features. One of the most significant of these features is a group of functions that offer new and powerful ways to create objects and manipulate their properties. "But creating objects and changing their properties is something I could already do in JavaScript," you say. This is true, but ECMAScript 5 addresses some subtleties that were previously not possible.

Object creation

JavaScript is a prototypal language, but as the wise Douglas Crockford has said, JavaScript itself is ambivalent about its prototypal nature. It has traditionally used a strange inheritance model based on C/Java syntax to mask what it's really doing. Previously, if you had a Parent object and wanted to create a new Child object which inherits from the Parent, you'd have to take multiple steps including assigning an instance of Parent to the Child object's prototype. In ECMAScript 5, this is simplified with the Object.create method. It works like this:

function Parent() { }
var parent = new Parent();
var child = Object.create(parent);

This creates a new object, child, which inherits from parent in one easy step. The argument to Object.create is the object to be used as the child's prototype. It can either be an object or null, if you don't want the new object to inherit from anything. Object.create is handy for creating a simple object which inherits from another, but its hidden power comes from its optional second argument.

Property definition

Up until now, there were certain subtleties of native objects that could not be emulated by pure JavaScript:

  • It was not possible for a property to react to assignment by engaging in behavior. An example of this is setting the innerHTML property of a DOM node. Assigning a string to this property would not only change the value of the property, but it would alter the contents of the element in the DOM.
  • Certain native object properties, such as toString, do not show up when iterating through the properties of a child object using a for...in loop. There was no way to create a property in JavaScript that behaved this way, forcing all for...in loops to use hasOwnProperty to restrict the loop to the child's objects non-inherited properties.

The optional second argument to Object.create is a set of property descriptors, which allow you to control the above behaviors and more. There are now two types of properties: data properties and accessor properties. Data properties are given an explicit value as we're used to, but accessor properties are given two functions to act as getter and setter methods instead. Here is what they look like:

var child = Object.create(parent, {
  dataDescriptor: { value: "This property uses this string as its value." },
  accessorDescriptor: {
    get: function () { return "I am returning: " + accessorDescriptor; },
    set: function (val) { accessorDescriptor = val; }
  }
});

Here we are defining two properties for the child object, dataDescriptor and accessorDescriptor. The dataDescriptor property in this case behaves almost the same as a traditional object property, and except for one subtlety which we'll explore in a moment, it is accessed and set like usual. The accessorDescriptor property, however, uses the supplied functions to get and set its value. Assigning a new value to the property calls the set function and passes in the value being assigned as the parameter. Accessing the property with the usual syntax actually runs the get function and the value returned is whatever the function returns. This allows the value of a property to have arbitrarily complex logic behind it.

There are three additional aspects of a property we can control, each given a boolean value:

  1. writable: Controls whether or not the property can be assigned. If true, attempts at assignment will fail. Only applies to data descriptors.
  2. enumerable: Controls whether or not this property will appear in for...in loops.
  3. configurable: Controls whether or not the property can be deleted, and whether its property descriptor (other than writable) can be changed.

Each of these defaults to false if not supplied. Here is the last code example again with various values for these fields:

var child = Object.create(parent, {
  dataDescriptor: {
    value: "This property uses this string as its value.",
    writable: true,
    enumerable: true
  },
  accessorDescriptor: {
    get: function () { return "I am returning: " + accessorDescriptor; },
    set: function (val) { accessorDescriptor = val; },
    configurable: true
  }
});

In this version, the dataDescriptor property can be assigned and will show up in for...in loops, but it cannot be deleted and its property descriptor cannot be altered further. accessorDescriptor, however, can be deleted and its property descriptor can be altered. The difference between defining a data property with Object.create and a property descriptor rather than simply writing child.dataDescriptor = "This property uses this string as its value"; is in the defaults for the three fields. When using Object.create, all three fields default to false, whereas using the classic style they all default to true.

It is, of course, possible to use property descriptors to create or alter properties after the creation of the object itself. To do this, you can use the new methods Object.defineProperty and Object.defineProperties. Object.defineProperty takes the object in question, the name of the property, and a single property descriptor as its arguments. Object.defineProperties lets you define multiple properties at once by passing the object in question and an object whose keys are each properties, just as we did with Object.create.

These new features in JavaScript offer power previously unattainable in creating and manipulating objects. In the next part, we will take a look at sealing and freezing objects.

Over-engineering for fun and learning

I love programming and web development. I like to always have a project to work on. This can be tough to achieve sometimes, because I'm not very good at coming up with ideas for things to build. I've completed some pretty cool projects in the past (like More Things Need To and to_lang), but I've been in kind of a slump for ideas lately.

Last week I watched A re-introduction to the Chrome Developer Tools with Paul Irish and, though I have used Safari for a long time, could no longer resist the siren call of Google Chrome and its fancy cutting-edge features. One of the things I like about Chrome is how Google is encouraging a developer culture with extensions, packaged web apps, and the Chrome Web Store.

I wanted to make an extension to learn a little about Chrome development, so I spent a few minutes brainstorming what I might create. I decided on an little tool to help improve my workflow at my job at Eventful. Our standard workflow on the front end team is to edit files locally with TextMate, then rsync them to a remote server where the actual virtual hosts are running. I have a Ruby script I use to automate the syncing process, but it still involves three steps: edit and save files, open the terminal and run the script, then refresh the browser to see the changes. I thought it would be cool to have a Chrome extension that would combine the second and third steps.

I ran into a snag in that extensions do not have access to the local filesystem nor the ability to run executables (which makes sense, since it would be a pretty severe security risk.) But for my purposes, I knew it was safe because I was the one writing it and would be the only one using it. Although you can't run a shell script directly from the browser, you can make network requests with XMLHTTPRequest. I whipped up a little Sinatra app that runs locally and listens for calls from the extension, then runs the sync script.

In the end, I had an extension that makes a little blue "E" icon appear in the omnibox whenever I'm on a page on one of my virtual hosts. Clicking it turns the icon yellow while it makes a request to the server. If an error occurs, the icon briefly turns red and logs a message to the console. If it succeeds in syncing, it refreshes the page to get the new changes. It was a highly over-engineered solution for what it actually accomplishes, but it was a fun exercise and allowed me to learn the Chrome extension system and do some problem solving.

I'll keep this experience in mind in the future when I'm aching for a project but can't think of anything to do that seems worthwhile. It's perfectly fine to do something that's not super useful. You'll still learn something and have fun doing it, regardless of whether or not the fruits of your labor have a practical purpose.

Why I don't like the CoffeeScript default in Rails 3.1

I generally don't like to comment on whatever the latest controversy is, but as both a front end developer and a Rails developer, I do want to jot down my thoughts on CoffeeScript becoming the default for JavaScript in Rails 3.1. While it's obviously not the end of the world, I disagree with this decision and I'm less than pleased about the way the core team has reacted to the community over it.

The main problem with this change is that using CoffeeScript is not the majority case. Most people do not use or want to use CoffeeScript, and all those people now have some additional boilerplate work to do every time they start a new app. Commenting the CoffeeScript gem out of the Gemfile to disable it is also unintutive compared to a command line option on app generation. It's not the most painful thing ever, but it's an extra step that must now be taken just to start with a clean slate. It sacrifices the convenience of the majority for the convenience of the minority, which goes against the rapid productivity and convention over configuration that people like me love Rails for.

I don't think this change is really about promoting the next greatest technology. I think it strongly illustrates that, in the end, Rails just uses whatever DHH prefers. There's only one explanation for why CoffeeScript should be default and SASS/Compass, HAML, and RSpec should not: it's DHH's opinion. If he happened to dislike CoffeeScript and like RSpec, I guarantee CoffeeScript would not be default and RSpec would.

A lot of people felt strongly about this change, and the Rails core team took a very "us against them" attitude in their response, calling people whiners, complainers, and trolls, who don't appreciate what has been given to them. I think that's a horrible, unconstructive attitude to take toward the very people who have made the framework successful: the users. People are empassioned about it because they love the framework and have a vested interest in seeing it continue to improve and succeed.

The arrogance of DHH and some of the other team members is by far my least favorite part of the Rails ecosystem. No single person is a god whose every opinion is correct just because of their past success. No one running an open source project should have a "you owe me" attitude about their users. If you have an entitlement complex, you're missing one of the most important aspects of open source software.

Page 8