Alexis Nikichine wrote:
The color example was just a gratuitous one. I was worrying
about arbitrary strings typed in by users. One day, I had
reports of "pop art" request failing. "pop" yielded a
function (instead of an expected integer).
<snip>
If you want to store key/value pairs without any restrictions to the
names perhaps you need to implement an object for that storage. Maybe in
a Java style with - get - and - put - (and maybe - remove -) methods.
With an internal naming system that allows referencing by key but does
not directly use that key as a property name. You could genuinely hash
the keys to a number and then represent that number as a string property
name, but that would be quite an overhead to add to each reference. A
satisfactory indirect use of the key might be to append a space
character to the front of it, producing a property name that will never
conflict with any prototype inherited property of an object. A simple
implementation might go:-
function SafeHash(){
var safeName = [' '];
var values = {};
this.get = function(key){
safeName[1] = key;
return values[safeName.join('')]
};
this.put = function(key, value){
safeName[1] = key;
values[safeName.join('')] = value;
};
this.remove = function(key){
safeName[1] = key;
delete values[safeName.join('')];
};
}
A more complex version, implementing most of the pertinent methods form
the Java Hashtable, and returning Enumerator (and Iterator) Interfaces
for keys and values, might go:-
var Hashtable = (function(){
var keyAdj = [' '];
/* This private static Object constructor is used to implement
a Java style Enumerator (and Iterator) Interface:-
*/
function Enumeration(arrNm, activeEnum, keysToIndex){
var lastIndex = null;
var enumIndex = 0;
while(typeof activeEnum[enumIndex] == 'number'){enumIndex++;}
activeEnum[enumIndex] = 0;
this.hasNext = this.hasMoreElements = function(){
if(activeEnum[enumIndex] < keysToIndex.tableLength){
return true;
}else{
if(typeof activeEnum[enumIndex] == 'number'){
activeEnum[enumIndex] = null;
}
return false;
}
};
this.next = this.nextElement = function(){
if(this.hasNext()){
lastIndex = activeEnum[enumIndex];
return keysToIndex[arrNm][activeEnum[enumIndex]++];
}else{
return null;
}
};
this.remove = function(){
if(typeof lastIndex == 'number'){
removeItem(keysToIndex._indexToKeys[lastIndex],
keysToIndex, activeEnum);
lastIndex = null;
}
};
};
function removeItem(key, keysToIndex, activeEnum){
keyAdj[1] = key;
key = keyAdj.join('');
var remIndex = keysToIndex[key];
if(typeof remIndex == 'number'){
delete keysToIndex[key];
keysToIndex.tableLength--;
for(var c = remIndex;c < keysToIndex.tableLength;c++){
keysToIndex._indexToValue[c] =
keysToIndex._indexToValue[c+1];
keyAdj[1] = (keysToIndex._indexToKeys[c] =
keysToIndex._indexToKeys[c+1]);
keysToIndex[keyAdj.join('')] = c;
}
keysToIndex._indexToValue.length = keysToIndex.tableLength;
for(var c = activeEnum.length;c--
{
if((activeEnum[c])&&(remIndex < activeEnum[c])){
activeEnum[c]--;
}
}
}
}
/* Hashtable object constructor fuction:-
*/
function HTable(){
var keysToIndex ={_indexToValue:[],_indexToKeys:[],tableLength:0};
var activeEnum = [];
this.get = function(key){
keyAdj[1] = key;
key = keyAdj.join('');
if(typeof keysToIndex[key] == 'number'){
return keysToIndex._indexToValue[keysToIndex[key]];
}else{
return null;
}
};
this.put = function(key, value){
keyAdj[1] = key;
var sKey = keyAdj.join('');
if(typeof keysToIndex[sKey] == 'number'){
keysToIndex._indexToValue[keysToIndex[sKey]] = value;
}else{
keysToIndex[sKey] = keysToIndex.tableLength;
keysToIndex._indexToValue[keysToIndex.tableLength] = value;
keysToIndex._indexToKeys[keysToIndex.tableLength++] = key;
}
};
this.remove = function(key){
removeItem(key, keysToIndex, activeEnum);
};
this.containsKey = function(key){
keyAdj[1] = key;
return (typeof keysToIndex[keyAdj.join('')] == 'number');
};
this.size = function(){return keysToIndex.tableLength;};
this.elements = function(){
return new Enumeration('_indexToValue',activeEnum, keysToIndex);
};
this.keys = function(){
return new Enumeration('_indexToKeys', activeEnum, keysToIndex);
};
}
HTable.prototype.clear = function(){
var e = this.keys();
while(e.hasNext()){
this.remove(e.next());
}
};
HTable.prototype.toString = function(){
var k,e = this.keys();
var st = '';
while(e.hasNext()){
k = e.next();
st += k+' = '+this.get(k)+'\n';
}
return st;
};
HTable.prototype.containsValue =
(HTable.prototype.contains = function(testVal){
var v,e = this.elements();
while(e.hasNext()){
v = e.next();
if(v === testVal){
//if((v == testVal)&&(typeof v == typeof testVal)){
return true;
}
}
return false;
});
HTable.prototype.isEmpty = function(){
return (this.size() == 0);
};
HTable.prototype.putAll = function(hTable){
if((typeof hTable == 'object')&&
(hTable.constructor == HTable)){
var n,e = hTable.keys();
while(e.hasNext()){
n = e.next();
this.put(n, hTable.get(n));
}
}
return this;
};
HTable.prototype.clone = function(){
return new HTable().putAll(this);
};
HTable.prototype.equals = function(o){
return (o == this);
};
return HTable; //return the Hashtable object constructor.
})();
The optimum functionality for your application will probably lye
somewhere in-between the two.
Richard.