详解JavaScript中Hash Map映射结构的实现
HashMap通常在JavaScript中作为一个简单的来存储键值对的地方。然而,Object并不是一个真正的哈希映射,如果使用不当可能会带来潜在的问题。而且JavaScript可能不提供本地哈希映射(至少不是跨浏览器兼容的),有一个更好的声明对象属性的方法。
HashMap的简单实现:
varhashMap={ Set:function(key,value){this[key]=value}, Get:function(key){returnthis[key]}, Contains:function(key){returnthis.Get(key)==null?false:true}, Remove:function(key){deletethis[key]} }
使用方法示例:
hashMap.Set("name","JohnSmith"); hashMap.Set("age",24); hashMap.Get("name");//JohnSmith hashMap.Contains("title");//false hashMap.Contains("name");//true hashMap.Remove("age");
在Object声明成员的问题
该问题可能缘于对象原型链的继承机制。就拿toString方法来说,如果使用in操作符来判断对象是否存在的话:
varmap={}; 'toString'inmap;//true
因为in操作符会从所有原型继续对象查找该对象是否存在。要解决这个问题,可使用hasOwnProperty方法检测该对象是否存在:
varmap={}; map.hasOwnProperty('toString');//false
这个方法可以工作地很正常,不过如果你定义了一个hasOwnProperty属性那可能就麻烦了:
varmap={}; map.hasOwnProperty='foo'; map.hasOwnProperty('hasOwnproperty');//TypeError
快速修复这个的方法是使用原生对象的方法。
varmap={}; map.hasOwnProperty='foo'; {}.hasOwnProperty.call(map,'hasOwnproperty');//true
这种方法不会引起任何问题,每次你判断对象中的属性是否存在时都要过滤掉原型链中的方法:
varmap={}; varhas={}.hasOwnProperty; for(varkeyinmap){ if(has.call(map,key)){ //dosomething } }
裸对象
创建一个真正的HashMap的诀窍是解藕所有的原型对象。我们可以通过Object.create来实现这个效果
varobj={}; //isequivalentto: varobj=Object.create(Object.prototype);
另外,这种方法可以让你完全放弃原型,直接使用null来继承。
varmap=Object.create(null); mapinstanceofObject;//false Object.prototype.isPrototypeOf(map);//false Object.getPrototypeOf(map);//null
这些裸对象(或字典)是作为HaspMap的理想选择。因为不会有任何冲突,它会抵制任何类型转换,比如这样就会产生错误。
varmap=Object.create(null); map+"";//TypeError:Cannotconvertobjecttoprimitivevalue
这里没有任何保留字,它就是为HashMap设计的,比如。
varmap=Object.create(null); 'toString'inmap;//false更进一步,for...in循环变得更加简单了,我们只需要把循环写成这样。
varmap=Object.create(null); for(varkeyinmap){ //dosomething }
除了这些区别,它使用起来跟一般的Object键值存储没有任何区别。该对象可以被序列化,可以声明原型和被继承,上下文变量的使用也是一样的。
varmap=Object.create(null); Object.defineProperties(map,{ 'foo':{ value:1, enumerable:true }, 'bar':{ value:2, enumerable:false } }); map.foo;//1 map['bar'];//2 JSON.stringify(map);//{"foo":1} {}.hasOwnProperty.call(map,'foo');//true {}.propertyIsEnumerable.call(map,'bar');//false
甚至上面提到的那些变量检测方法同样适用。
varmap=Object.create(null); typeofmap;//object {}.toString.call(map);//[objectObject] {}.valueOf.call(map);//Object{}