Dustin's Pages

Saturday, May 9, 2015

JavaScript Objects from a Java Developer Perspective

One of the challenges for Java developers learning and applying JavaScript is the very different interpretations each language has of "objects." I find it much easier to context switch between Java and languages such as Groovy, Ruby, Python, C#, and C++ than switching context between Java and JavaScript. The other languages' class-based approach to object-orientation is similar enough that their differences are primary syntax and syntax is relatively easy to learn and switch context on. JavaScript syntax in many ways is actually more like Java's than some of these languages, but its prototype-based approach to object-orientation is very different. In this post, I look at JavaScript "objects" from a Java developer perspective and offer some tips and tactics Java developers can use to better bridge the concepts of object-oriented Java and object-oriented JavaScript.

Whereas Java and several other programming languages have a wide and rich range of datatypes and collections types, JavaScript only has a small number of primitive datatypes (Boolean, Number, String, Null, and Undefined), a single collection-like data structure (array), and supports JavaScript objects. This makes it simpler to learn these basic datatypes and array collection, but means additional work (or a framework) is required to implement specific functionality that other languages' types and collections might provide.

Constructor Function Approach for Instantiating JavaScript Objects

There are multiple approaches for instantiating JavaScript objects. As a Java developer, I prefer the "constructor function" approach. One of its most significant advantages is that the objects created with this approach can be used by multiple pieces of code because it is named and available for their use. However, as a Java developer, this approach appeals to me because it is the most like Java (and other class-based object-oriented languages).

The next two code listings contrast two common approaches for instantiating JavaScript objects (object initializer and constructor function).

JavaScript Object Instantiation via Constructor Function

// Person objects are instantiated with a 'constructor function' approach.
// With this approach, more than one instance of 'Person' can easily be
// instantiated as needed via the "new" keyword. It is convention to use
// uppercase for the first letter of the function to indicate that it's a
// 'constructor function'.
function Person(lastName, firstName)
{
this.firstName = firstName;
this.lastName = lastName;
}
var person = new Person('Clouseau', 'Jacques');
console.log('The person is ' + person);

Constructing a JavaScript object with a "constructor function" allows it to be referenced by name, allows the familiar "new" keyword to be used, and, when the function's name begins with a capital letter, looks like a convention that would fit in Java.

JavaScript Object Instantiation via Object Initializer

// The 'object initializer' approach is used here to define an "individual"
// object. This is a one-time approach because it's not named and is less
// like approaches in class-based object-oriented languages.
var individual = {}
individual.lastName = 'Panther';
individual.firstName = 'Pink';
console.log('The animated character is ' + individual);

The object initializer approach is a single-use approach because there is no named function to be referenced for a separate instantiation. Its syntax is also quite a bit different than that we're used to in Java.

In the previous screen snapshot, the names that were displayed in the console were both shown as "[object Object]". Like Java, all objects in JavaScript extend a common object called Object. In particular, all JavaScript objects inherit properties from Object.prototype. In this case, Object.prototype.toString() provides a default string representation for all JavaScript objects. As the screen snapshot demonstrates, it's only minimally valuable (similar to how Java objects' default toString() implementations inherited from Java's java.lang.Object are minimally valuable).

Just as one can override toString() in Java classes so that objects provide useful data on themselves, objects instantiated with constructor functions can override their Object.prototype.toString() implementations. The next code listing adapts the example above on constructor function and adds code to override the toString() (see lines 12-15).

Overriding JavaScript Object's toString() Implementation

// Person objects are instantiated with a 'constructor function' approach.
// With this approach, more than one instance of 'Person' can easily be
// instantiated as needed via the "new" keyword. It is convention to use
// uppercase for the first letter of the function to indicate that it's a
// 'constructor function'.
function Person(lastName, firstName)
{
this.firstName = firstName;
this.lastName = lastName;
}
Person.prototype.toString = function personToString()
{
return this.firstName + ' ' + this.lastName;
}
var person = new Person('Clouseau', 'Jacques');
console.log('The person is ' + person);

The new output for the instance of Person using this overridden toString() is shown in the next screen snapshot.

In the example just covered, I overrode the prototype specifically for Person. This is a nice localized use of the ability to override objects' prototype. A broader (and potentially much more dangerous) capability is presented by being able to override Object.prototype and thus affect all JavaScript objects' behaviors. This is analogous to the risks and rewards one would get in Java if able to override java.lang.Object's behaviors one time for all Java objects. In other words, if you imagine java.lang.Object's behaviors being changeable at that level, that's what JavaScript's Object.prototype allows.

Avoiding JavaScript's Ubiquitous Global Scope

JavaScript makes it far too easy to make variables global scope. Effective use of constructs such as the var and this keywords can help. Variables inside JavaScript functions are limited to those functions' scope when they are designated var. In contrast, variables declared within a function without var are not limited to that function's scope and so any changes anywhere can change the "state" of that function. This is an idea that probably makes most C++ and Java developers cringe. The this keyword is surprisingly difficult in JavaScript because it varies greatly depending on how used, how called, and whether in strict mode or not. In other words, JavaScript's use of this is far more difficult than Java's (which is essentially a reference to a particular instance's class members). However, my usage of this above (in the function constructors) is not surprising and is described in Mozilla Developer Network JavaScript Reference, "When a function is used as a constructor (with the new keyword), its this is bound to the new object being constructed."

JavaScript Objects are Like Java Maps

Although most browsers now support a first-class map and the forthcoming EMCAScript specification spells one out, previous versions of standard JavaScript has relied on arrays for collections needs. However, most introductions to JavaScript objects describe them as collections of name/value pairs that are very similar to maps. Indeed, some of the references that introduce JavaScript from a Java developer have shown Java Maps with String keys and Object values to illustrate functionality provided by JavaScript objects.

Conclusion

Despite seemingly common syntax and even sharing four letters in their name, Java and JavaScript are very different in many ways. In particular, while both can be said to be object-oriented or object-based, their very different manners of implementing objects (class-based versus prototype-based) has significant impacts on the respective languages. Understanding some of the basic similarities and differences between these two languages' objects can help make context switching between the two languages easier. It's also worth noting that there are plans for ECMAScript 6 (Harmony) to provide some semblance of support for semantics familiar to those who have used other object-oriented languages. For example, there is a proposal for classes in ECMAScript 6.