May
17
2011

Dojo Beginner Gotcha: Accidental Static Fields

Many developers with a background in Software Engineering are drawn to Dojo because of its ability to simulate classical type declaration and inheritance structures via dojo.declare. Specifying a ‘class’ via dojo.declare is straight-forward, but an extremely common mistake involves the accidental declaration of a ‘static field’ (i.e. a class variable) when intending to declare an instance variable. Let’s jump right into an example to demonstrate.

Mustang Sally

Let’s use dojo.declare to specify a ‘class’ for an Automobile. This Automobile will have five instance variables and zero class (static) variables. The Automobile will have two String variables for its make and model, a Number variable for its year, an Array variable for a list of options, and an Object variable to describe its engine. One might think that a sensible way to declare this type would be something like:

dojo.provide("machines.Automobile");

dojo.declare("machines.Automobile",  null, {
	/* summary:
	 * 		VROOOOM!
	 */

	make: "",
	model: "",
	year: null,				// YYYY
	options: [],
	engine: {
		horsepower: 0,
		size: 0
	}

	//...

});

Some of these instance variables are declared properly, but a few aren’t. Can you guess which ones are a problem? We’ll need to create an instance of our class to try it out. But first lets add a print() function to our “Automobile” class for convenience sake:

	print: function() {

		// Dump the properties into the console
		console.log("%s %s %s. %s. %s hp, %f L."
				,this.year
				,this.make
				,this.model
				,this.options.toString()
				,this.engine.horsepower
				,this.engine.size
		);
	}

Let’s write some code to bash this ‘class’ around a bit. Let’s create an Acura Integra with 160 horsepower and a 1.7 Liter engine. We’ll then call print on the Integra. Next, we’ll do the same for the 127 horsepower Honda Civic. Lastly, we’ll call print again on the Integra.

	// Make a car
	var teg = new machines.Automobile();

	// Bring in the competition
	var civette = new machines.Automobile();

	// Set some properties on the first car
	teg.year = 1992;
	teg.make = "Acura";
	teg.model = "Integra";
	teg.options.push("Sunroof");
	teg.engine.horsepower = 160;
	teg.engine.size = 1.7;

	teg.print();

	// Set some properties on the second car
	civette.year = 1998;
	civette.make = "Honda";
	civette.model = "Civic";
	civette.options.push("Power Steering");
	civette.engine.horsepower = 127;
	civette.engine.size = 1.6;

	civette.print();

	// How's the teg doing?
    console.log("\tAfter setting the properties on the Civic, let's check back in on the Acura...");
	teg.print();

The output in Firebug’s console looks like this:
Firebug Console

Hold your horses! Who swapped the Integra’s motor for the Civics’?! Problem? Indeed! Properties of the first car were overwritten by the second. Notice that the only properties that were overwritten were those that were instantiated as an array or object. So, if you guessed earlier that options and engine were the troublesome fields, you shouldn’t be reading this tutorial as you are a Dojo genius! The moral of the story is to be wary of instantiating properties as types other than the simple string, number, or boolean. Any fields declared in-line as complex objects will be treated as ‘static’ properties shared between all instances of that type.

This is How We Do It

If you would like to use a complex object as a field, hope is not lost. The proper way to do this is to set the variable to null initially, and then instantiate it in your type’s constructor function like so:

//...
	make: "",
	model: "",
	year: null,				// YYYY
	options: null,
	engine: null,

	constructor: function() {
		this.options = [];
		this.engine = {
				horsepower: 0,
				size: 0
		};
	},
//...

But hey, don’t hate on statics just yet! The concept of static class properties can be very useful. In our example, let’s assume that every Automobile had a standard set of options that was identical for each (A/C, cruise control, sunroof, etc) and we wanted to create an enumeration of these options. This would be an ideal candidate for a static array. The key to effective use of dojo.declare is to avoid accidentally creating a static variable when an instance variable is intended. Another important skill is to recognize when you have made this mistake. It is very easy to make and even veteran Dojo-slingers do it. If during a debugging session, you ever encounter a scenario where the properties of one instance of a dojo.declare‘d type is trampling another’s, you should immediately think ACCIDENTAL STATIC ALERT!

Static Electricity

Related Posts

About the Author: Miko Borys

Miko is a Computer Engineering student at Virginia Tech minoring in Computer Science. He is the creator of a new music discovery site: AudioQuarium.

7 Comments + Add Comment

  • So, why are complex objects treated differently than simple objects in dojo.declare statements?

  • Yup, this has bitten me and it took a bit of digging to uncover why it went wrong. Good post!

  • It’s worth noting that the legacy parser (that is, dojo.parser as it behaved <= 1.5 and continues to behave with dojoType in all 1.x) actually treats the value of a tag attribute differently, if the prototype member it matches is initialized to an array, versus null or an object. The former case takes the input string and splits it by comma; the latter two will simply be run through dojo.fromJson. (There are also distinct behaviors for strings, numbers, booleans, functions, dates, and URLs.)

    Example illustrating this: http://jsfiddle.net/X3WBT/ – note how the prop attribute values in the div tags are slightly different but the end result in the console output is the same for all 3.

    That said, it is absolutely imperative that any "non-static" array or object properties be initialized in the constructor (or some other instance method that runs before these properties are ever manipulated). This will ensure that the properties are adequately shadowed on each instance created based off of the prototype.

    I had gotten in the habit of null-initializing anything that was to become an array or object on the instance as well, specifically to deter myself and others from this pitfall, but eventually realized the minute difference with the parser behavior. Of course, if you're not interested in declaratively invoking this constructor ever, or you're already set on the Dojo 1.6+ parser behavior with data-dojo-type and data-dojo-props, then it's a non-issue and you can initialize to null all you want.

    @Chris: it's not so much anything to do with dojo.declare specifically, but prototypal inheritance in general. If the property exists on the prototype but is never explicitly *set* on the instance, it's still technically referring to the prototype's property value. The moment you do this in an instance method:

    this.prop = [];

    You're immediately "shadowing" the prototype's 'prop' value with a value on this instance itself. However, if you access this.prop on the instance without ever shadowing it, and call methods on it which manipulate it, you're technically still manipulating the "shared" object on the prototype, and thus the change is reflected across other objects inheriting that prototype.

  • Ah Miko – I see Dan persuaded you to write an article on his site :) Nice one.

    I found this a source of some confusion as well, really because dojo.declare encourages you to program like it’s Java when of course it is something totally different.

    I actually consider it dangerous to list the variables in a java field style at the top of the dojo “class”. These are effectively comments, so why not just use a comment ? if you don’t ever set or use one of them, or you use a new one in the constructor but forget to declare it, everything still works, they just rot.

    The same problem you describe happens with arrays as well e.g supposing I had a crappy list for some reason :

    dojo.declare(“List”, null, {

    list: [],

    addItem: function (item) {
    this.list.push(item);
    }

    });

    What happens here is that the list array is a property of the prototype of every object created when you use new to invoke the function e.g. new List();

    In the addItem function The JS engine will not find the property “list” on “this” so it looks at the prototype to see if it has one called list, which it does thanks to the Java style field listing and uses that one, reference resolved.

    By initialising the array in the constructor e.g.

    dojo.declare(“List”, null, {

    // properties of a new List
    // list (Array)

    constructor: function () {
    this.list = [];
    },

    addItem: function (item) {
    this.list.push(item);
    }

    });

    It works as expected and this.list hides this.__proto__.list. Presumably this fashion for listing the properties each instance will have is only done to make it more familiar to Java or C++ developers as there is no need to do it at all and infact it could be argued it wastes memory. e.g. if I have this :

    dojo.declare(“List”, null, {

    list: [],

    constructor: function () {
    this.list = [];
    },

    addItem: function (item) {
    this.list.push(item);
    }

    });

    We now have :

    var foo = new List();

    foo.list; // array
    foo.__proto__.list // array

    My advice would be to initialise all properties in the constructor, then if you want to list them at the top for readability (which is presumably why they are there) then you can add them to the top of the “class” later on, you could even just use a comment. If you actually wanted a static variable stick it on the constructor function itself e.g.

    List.STATIC_VALUE = “foobar”;

    or if you really meant to manipulate a property on the prototype e.g. for caching, then you could explicitly write that, e.g.


    expensiveFunction: function () {
    // assuming expensive result can be
    // calculated once and will always be the same

    if (!this.expensiveResult) {
    // share this result with every List…
    List.prototype.expensiveResult = … expenisve operation
    }
    }

    Note that properties inherited from the prototype are read only so as soon as you use = on a property of “this” the prototype’s property with the same name is hidden.

  • Thanks to all of you who contribute. This site continues to impress me with the depth and quality of information. The tutorials are simple yet extremely informative, and the comments are invaluable. This really helps those of us who are now in the Dojo world and want to write good/tight code. Keep up the good work.

  • worth noting that your constructor should handle the case of mixing in new statics from literals, should the user choose to do so.

    constructor: function(args){
    args = args || {};
    this.options = args.options || [];
    this.engine = args.engine || { … }
    }

    this way when you new Thinger({ options:[1,2,3], engine:{ ftw:”!!!” } }); the actual constructor args are used, and you avoid the “static” nature of objects by ref.

  • Its worth reading this. Really nice. Thanks Miko Borys.

Leave a comment