Bleh. It's a major bummer you can't overload the indexer operator in JavaScript. Oh well. We'll just have to be creative and come up with a different solution. This is a good thing (and fun). :-)
LLer, the solution you settle with is a damn good one. Kudos. It's refreshing to come across people who truly understand JavaScript to that degree.
After reading this question I was struck with an epic idea and wrote up some code for the fun of it. My solution to the problem is very similar to yours and many others that have been done before. But, I feel I came up with something unique and very neat so I want to share it.
So I'm hosting a project of mine on CodePlex where I use a very jQuery-esque technique for defining properties (self-contained getter/setter functions) very similar to the one you're using. For the solution I came up with I simply just extrapolated from this pre-existing code of mine. Here's my approach toward the lazy loading of array indexes. Starting from the beginning...
Let's consider a property named "PageSize". Here's how the property would be used with my technique:
var MyClass = function() { }; // MyClass constructor.
var instance = new MyClass();
instance.PageSize(5);
alert(instance.PageSize());
Take notice that the property is a single function where providing a value as the first parameter invokes the setter and leaving out the parameter invokes the getter. The "PageSize" property would be defined as part of the MyClass class like so:
MyClass.prototype.PageSize = function(v) { return this.GetSetProperty("PageSize", v, myFunctionThatDoesLazyLoading); };
The property function is merely a wrapper around a call to the utitily GetSetProperty method which does the actual getting and setting. Here's a snippet of the GetSetProperty function:
Object.prototype.GetSetProperty = function(name, value, loadFunction) {
if (!this.Properties)
{
this.Properties = {};
}
if (value)
{
this.Properties[name] = value;
}
else
{
if (!this.Properties[name] && loadFunction)
{
this.Properties[name] = loadFunction();
}
return this.Properties[name]; // This will return "undefined" if property doesn't exist or the loadFunction wasn't provided.
}
};
So that handles properties. But, to provide a means to access indexed values of a possible property of Array type I modify this code further like so:
Object.prototype.GetSetProperty = function(name, value, loadFunction, index) {
if (!this.Properties)
{
this.Properties = {};
}
if (typeof index === "number" && this.Properties[name] && this.Properties[name].constructor == Array)
{
return this.GetSetArrayProperty(name, index, value, loadFunction);
}
else
{
value = index;
}
if (value)
{
this.Properties[name] = value;
}
else
{
if (!this.Properties[name] && loadFunction)
{
this.Properties[name] = loadFunction();
}
return this.Properties[name]; // This will return "undefined" if property doesn't exist or the loadFunction wasn't provided.
}
};
Object.prototype.GetSetArrayProperty = function(name, index, value, loadFunction) {
if (value)
{
this.Properties[name][index] = value;
}
else
{
if (!this.Properties[name][index] && loadFunction)
{
this.Properties[name][index] = loadFunction();
}
return this.Properties[name][index];
}
};
The prototype declaration would need to be modified like so:
MyClass.prototype.PageSize = function(i, v) { return this.GetSetProperty("PageSize", v, myFunctionThatDoesLazyLoading, i); };
Everyone reading this can access a working set of the code here: http://jsbin.com/ajawe/edit
Here's a complete listing of the code with tests:
Object.prototype.GetSetProperty = function(name, value, loadFunction, index) {
if (!this.Properties)
{
this.Properties = {};
}
if (typeof index === "number" && this.Properties[name] && this.Properties[name].constructor == Array)
{
return this.GetSetArrayProperty(name, index, value, loadFunction);
}
else
{
value = index;
}
if (value)
{
this.Properties[name] = value;
}
else
{
if (!this.Properties[name] && loadFunction)
{
this.Properties[name] = loadFunction();
}
return this.Properties[name]; // This will return "undefined" if property doesn't exist or the loadFunction wasn't provided.
}
};
Object.prototype.GetSetArrayProperty = function(name, index, value, loadFunction) {
if (value)
{
this.Properties[name][index] = value;
}
else
{
if (!this.Properties[name][index] && loadFunction)
{
this.Properties[name][index] = loadFunction();
}
return this.Properties[name][index];
}
};
// Here's a nifty function that declares the properties for you.
Function.prototype.CreateProperty = function(propertyName, loadFunction) {
eval("this.prototype['" + propertyName.toString() + "'] = function(i, v) { return this.GetSetProperty('" + propertyName.toString() + "', v, " + eval(loadFunction) + ", i); };");
};
var myFunctionThatDoesLazyLoading = function() {
return "Ahoy!";
};
var MyClass = function() { }; // MyClass constructor.
MyClass.prototype.PageSize = function(i, v) { return this.GetSetProperty("PageSize", v, myFunctionThatDoesLazyLoading, i); };
var instance = new MyClass();
alert(instance.PageSize()); // PageSize is lazy loaded.
instance.PageSize(5); // PageSize is re-assigned.
alert(instance.PageSize()); // Returns the new value.
instance.PageSize([1, 2, 3]); // PageSize is re-assigned to have an Array value.
alert(instance.PageSize(2)); // Returns the value at index 2 of the Array value.
instance.PageSize(2, "foo"); // Re-assigns the value at index 2.
alert(instance.PageSize(2)); // Returns the new value at index 2.
MyClass.CreateProperty("NewProp", function() { return ["a", "b", "c"]; }); // Demo of the CreateProperty function.
alert(instance.NewProp());
alert(instance.NewProp(1));