Saturday, May 23, 2015

A Java Developer's Perspective on the Power and Danger of JavaScript's Object Prototype

In the Anti-Patterns section of the book Learning JavaScript Design Patterns, author Addy Osmani calls "Modifying the Object class prototype" a "particularly bad anti-pattern." One of the interesting (and scary) aspects of this is that a developer can change the behavior for all JavaScript objects with one definition. This is analogous to what would be possible in Java if a Java developer was allowed to change Java's Object class.

I mentioned this risky feature briefly in my post JavaScript Objects from a Java Developer Perspective. Imagine the havoc that could be rendered if one was able to change, for example, how Java's Object's default equals(Object) implementation was implemented. In the blog post I just mentioned, I demonstrated overriding a particular Java object's toString() implementation. I mentioned, but did not demonstrate, overriding toString() for all JavaScript objects via Object.prototype. In this post, I do demonstrate this, which is the equivalent of what a Java developer could do in Java if allowed to change Java's Object's toString() implementation directly (Java developers can only extend Object and override it on per-class basis).

It's all too easy to change the default behavior of all JavaScript objects. The next code listing shows how easy it is to change the default JavaScript toString() behavior from providing the string "[object Object]" to providing the string "I'm a JavaScript object!"

Overriding All JavaScript Objects' Default toString() Implementations
Object.prototype.toString = function objectToString()
{
   return "I'm a JavaScript object!";
}

The simple four lines in the above code listing (and I could have easily had them all on a single line) change the default behavior of toString() for all JavaScript objects. I can still override this default implementation of toString on a per named object basis (there are no classes in JavaScript as of today). This was demonstrated in my previous post and is reproduced here for convenience:

Overriding toString() Implementation for Person Object Only
function Person(lastName, firstName)
{
   this.firstName = firstName;
   this.lastName = lastName;
}

Person.prototype.toString = function personToString()
{
   return this.firstName + ' ' + this.lastName;
}

The code listing above shows creation of a JavaScript Person object with a constructor function and shows overriding toString() for that newly created Person object. The next code listing demonstrates testing of the toString() implementations in such a way that the overridden default implementation and the customized Person implementations are rendered. The output of running this demonstration code is shown after the code.

Demonstrating Overridden Default toString() and Customized Person toString()
function demonstrateObjectPrototype()
{
   var indy = new Person('Jones', 'Henry');
   console.log("Indiana Jones's real name is " + indy);
   
   var solo = {};
   solo.lastName = 'Solo';
   solo.firstName = 'Han';
   console.log("Chewbacca's buddy is " + solo);
}

From the output shown above and the code listing before it, we can see that we have changed the default toString() from "[object Object]" to "I'm a JavaScript object!" and that we can still override a particular object's implementation to use its own customized behavior rather than the default behavior.

It is easy to see how this ability to easily manipulate the default behavior for all JavaScript objects can be both alluring and frightening. It wouldn't be a repeated "pattern" (even if it's an anti-pattern) if it didn't have appeal. Java's default Object.toString() implementation that provides the system identity hashcode of the object upon which it's called rarely seems helpful other than for differentiating it from other objects of the same type. It might be tempting at first, if one could easily change Java's Object's toString(), to change this implementation to use recursion to iterate over all of a given object's data members. However, there would also be significant risks and questions:

  • How would one prevent the toString() that used reflection from showing fields' values that should not be shown for security or other reasons?
  • Would class-level (static) data members be shown in addition to instance-level members?
  • Should all objects pay the reflection performance cost, especially when these objects might include collections of other objects that might lead to reflection on deep collections?
  • What would the preferred output format be?

These questions and concerns regarding overriding default Object.toString() behavior in Java are only a subset of the questions and concerns one might have and it could be argued that changing toString()'s default behavior is less risky than changing the default behavior of other Object methods such as equals(Object). One could always override the behavior in Java of changed default Object implementations, but it would need to be overridden in every extended class either directly or through its ancestor classes. Developers new to the code base might assume the JDK default Object behaviors and realize a nasty surprise when they find out that the codebase has changed default Object behaviors.

In this post, I have demonstrated how easy it is to override JavaScript's default Object behaviors via use of Object.prototype and have tried to also show why this should be rarely or never used. I have intentionally approached this from a Java developer's perspective in an effort to articulate more differences in the object models between Java and JavaScript.

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.

The next screen snapshot indicates how the above code listings are rendered in Chrome's JavaScript Console:

Adding toString() to JavaScript Objects

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.

Additional Resources