Tuesday, April 7, 2009

JavaScript Static Collection - A New Type Of Variable

Nowadays, I am working over a crazy manager between an XML with form information, another XML with custom rules to drive the XML with form information (DropDown onchange events, validation, and much more) and both to drive a runtime Ext.form.FormPanel with runtime components manipulated runtime via those two XML and over regular (x)HTML ... a runtime nightmare, but it is not the point.

While I was working over an Easy XPath Library for (x)HTML, trying to obtain best performances with the black sheep Internet Explorer, I ended up in an msdn page which explains the attributes HTML Element property.

Funny enough, that kind of property has truly a weird behavior, it is between VBScript and JavaScript. There are two ways to access its key/value pairs, the normal and logical one, plus the WTF!!! one. Example:

for(var
attrs = document.body.attributes,
len = attrs.length,
i = 0;
i < len;
++i
)
attrs(i) === attrs[i]; // true

The first though was: damn it, somebody poisoned my coffee again! (where the first time was when I read about Microsoft adopting WebKit as browser engine ...) but after few millisecond of brain delay, I realized that was brilliant!

The StaticCollection Type


Every Function in JavaScript has a length property, which tell us how many arguments that function accepts. In a completely dynamic language as JS is, this property has probably never been used, specially because every function has an ArrayObject arguments variable injected for each function call (but I guess would be interesting to use checks like: arguments.length > arguments.callee.length, for secret extra arguments to send ... anyway ... ).

Peculiarity of this property, we cannot overwrite it!
In few words, it is possible to create an Object via Function with immutable access over a collection, array, ArrayObject variable. This is the code:

var StaticCollection = (function(
name, // function name to assign
join // toString method from Array.prototype
){
// (C) WebReflection - Mit Style License
return function(){
var args = arguments, // trap sent arguments
length = args.length, // just a shortcut
i = 0, // used to assign values in order
callback; // Function to create and return
eval(
// create a function with a $name and $length arguments
// to block its length property
"callback = function ".concat(
name, "(",
new Array(length + 1).join(",$").substring(1),
// return first sent key from external arguments
"){return args[arguments[0]];}"
));
// assign in order properties via index ([0], [1], etc)
while(i < length)
callback[i] = args[i++];
// set the name for those browser where
// the function name is not present (mainly IE)
callback.name = name;
// set the useful toString method
callback.toString = join;
// return the FunctionObject
return callback;
};
})("StaticCollection", Array.prototype.join);

Due to fixed length behavior, the evaluation is absolutely necessary but not scary at all, since the string does not contain anything important, it is a simple wrap.
For those in Rhino or with some arguable rules about eval, here there is the intermediate step via Function:

...
i = 0,
callback= Function(
"args",
"return function ".concat(
name, "(", new Array(length + 1).join(",$").substring(1),
"){return args[arguments[0]];}"
)
).call(this, args);
...

which result is exactly the same but with a little bit of overhead caused by useless runtime Function execution.

StaticCollection Goals


  • one variable with static properties access plus indexed

  • indexed and called properties are comparable

  • indexes are mutable, perfectly suitable for relations between different objects/nodes/values

  • is the only quick and dirty way I know to make a collection immutable

  • something else, you'll find out :D



The StaticCollection Weirdness In Action


// Creation: same result
var sc1 = new StaticCollection("a", "b", "c"),
sc2 = StaticCollection("a", "b", "c"),
sc3 = StaticCollection.call(this, "a", "b", "c"),
sc4 = StaticCollection.apply(this, "abc".split(""))
;
/** [sc1, sc2, sc3, sc4].join("\n");
a,b,c
a,b,c
a,b,c
a,b,c
*/


// Properties Access
var sc = new StaticCollection("a", "b", "c")
sc(0); // "a"
sc[0]; // "a"
for(var key in sc){
key; // "a"
break;
};


// Immutable Properties: length + access via call
var sc = StaticCollection("a", "b", "c");
sc.length = 0;
sc.length; // 3
sc(1); // "b"
// sc(1) = 123; // error!


// Multiple access manifest
var sc = StaticCollection(456);
sc[0] === sc(0); // true
sc[0] = 123;
sc(0); // 456
sc[0]; // 123
for(var key in sc)
sc[key] !== sc(key); // true


// Type: function
typeof StaticCollection(1,2,3);


// Native toString: [object Function]
Object.prototype.toString.call(StaticCollection());


// Array Convertion:
var sc = StaticCollection(1,2,3);
sc.slice = Array.prototype.slice;
var a = sc.slice();
var b = Array.prototype.slice.call(
StaticCollection(4, 5, 6)
);


// Index Sort
var sc = StaticCollection(2, 1, 3);
sc; // 2, 1, 3
sc[0]; // 2
Array.prototype.sort.call(sc, function(a, b){
return a < b ? -1 : 1;
});
sc; // 1, 2, 3
sc[0]; // 1
sc(0); // 2


// Double indexOf
StaticCollectionSearch = function(value){
for(var
result = [-1, -1],
length = this.length,
i = 0;
i < length;
++i
){
if(this(i) === value){
result[0] = i;
break;
}
};
for(i in this){
if(/^\d+$/.test(i) && this[i] === value){
result[1] = i >> 0;
break;
}
};
return result;
};
var sc = StaticCollection(2, 1, 3);
Array.prototype.sort.call(sc, function(a, b){
return a < b ? -1 : 1;
});
StaticCollectionSearch.call(sc, 1); // 1, 0


// Array prototypes behaviour example
var sc = StaticCollection(1,2,3);
Array.prototype.shift.call(sc);
sc; // 2,3,
sc[0]; // 2
sc(0); // 1
sc.length; // 3


Crazy enough? The manually minified version is under 300 bytes, so let's start to play with ;)

/*WebReflection*/(function(n,j){this[n]=function(){var a=arguments,l=a.length,i=0,f;eval("f=function "+n+"("+new Array(l+1).join(",$").substring(1)+"){return a[arguments[0]]}");while(i<l)f[i]=a[i++];f.name=n;f.toString=j;return f}})("StaticCollection",Array.prototype.join);

Have fun!

No comments:

Post a Comment