我们知道JScript的对象(不只是Object)可以像html元素对象一样,添加任意的自定义属性值。也就是说JScript的对象,天生都是Key/Value map。并且这种map结构由于使用Native的代码实现,检索效率非常之高,我曾在 这篇文章里讨论过。不过在遍历这样的map时,expando导入属性和prototype导入属性存在了混淆。
由于JScript的prototype特性对对象的扩充非常的方便,所以我们在制作一些JScript类库的时候,一般都会使用prototype特性为对象添加方法,比如我们对Object进行如下prototype扩充:
Object.prototype.Clone
=
function
() {};
Object.prototype.Call
=
function
() {};
Object.prototype.OtherMethod
=
function
(){};
这个时候如果再使用Object作为map结构来使用,我们就会遇到遍历这个map的错误,看下面的代码:
var
objMap
=
{};
objMap['abc']
=
'
1
.abc';
objMap['def']
=
'
2
.def';
objMap['ghi']
=
'
3
.ghi';
objMap['jkl']
=
'
4
.jkl';
遍历这个集合:
function
DisplayMap(map)
{
var
values
=
[];
for
(
var
key
in
map )
{
values.push(map[key]);
}
return
values;
}
Display(objMap);
我们发现,在values里的值居然是:
function
(){},
function
() {},
function
() {},
1
.abc,
2
.def,
3
.ghi,
4
.jkl。真是郁闷
! 其实这就是 for in 语句的效果,JScript也就是这么设计的,我们没有办法改变。那么我门能不能只取出objMap中expando进去的属性呢?
由于prototype属性的优先级很高,在对象实例生成的时候就expand到对象实例中去了。所以我们建立的任何一个对象,都会包含相同的prototype属性。这样一来就好办了,我们把objMap中的prototype属性找出来过滤掉就行了呗。参考代码如下:
function
GetExpandoValues(map)
{
var
values
=
[];
var
obj
=
new
map.constructor();
for
(
var
key
in
map )
{
if
( obj[key]
!==
map[key] )
{
values.push(map[key]);
}
}
return
values;
}
GetExpandoValues(objMap);
获得结果为:1.abc,2.def,3.ghi,4.jkl。
=====================================================================
#3楼 回复 引用 查看
程序有问题。
第三行的 objMap 应为 map。
另外这样子之可以得到属性的值,不能得到属性的名字了。我修改了一下,并且prototype到了Object上:
Object.prototype.getExpandoValues = function() {
var values = new Array();
var obj = new this.constructor();
for(var i in this) {
if(obj[i]!=this[i]) {
values.push({"name":i, "value":this[i]});
}
}
return values;
}
效率可能不够高,但是这样就可以使用属性的名字了。
#5楼 回复 引用 查看
只有在for in的时候才需要这个方法,有了那种方法也是可以直接var value = objMap[key]的啊。
昨晚又修改了一下,回到原点:for in就是为了不遍历到prototype的成员,那么只要用一个变量存储expando属性的“名字”就可以了。属性的值完全可以通过取得的名字直接取得,不需要再存在数组中了。而把名字存储在数组中是因为没有一个有效的办法避过prototype的成员。
Object.prototype.getExpandoNames = function() {
var values = new Array();
var obj = new this.constructor();
for(var i in this) {
if(obj[i]!=this[i]) {
values.push(i);
}
}
return values;
}
使用:
// 假设已有一个对象objMap
var e = objMap.getExpandoNames();
for(var i=0;i<e.length;i++) { // 通过成员名字遍历对象
document.write("name:"+e[i]+", value:"+objMap[e[i]]); // 取值使用 objMap[e[i]]
}
这样在修改以前的for in语句的时候也简单,添加一个变量存储属性名字,改一下for()中的内容,再把循环中的以前的变量(比如i)全部改成e[i]这种形式就可以了。
#6楼 [楼主] 回复 引用 查看
@宇义
嗯,不错。这样更加像.NET里的hashtable了,我们除了可以foreach in hashtable外,还可以取hashtable.Keys。这里这个Keys就等同于你取出的Names。
#12楼 回复 引用 查看
什么叫expando?
原本以为是开发人员自己添加的对象属性。但昨天这个例子让我反思
注意以下的unselectable属性,它是dhtml可以解析的,但expando=false时,这个属性将失效。显然dhtml也有自己的expando属性。这样概念便模糊了。请大家澄清一下。
<HTML>
<HEAD>
<SCRIPT>
//Set the expando property to false.
document.expando = false;
</SCRIPT>
</HEAD>
<BODY>
<DIV>
<SPAN id="oSpan" unselectable="on">
This text should be selectable.</SPAN>
</DIV>
</BODY>
</HTML>
#13楼 [楼主] 回复 引用 查看
@eagle ov sea
Remarks for Unselectable Attribute:
Note Setting the
UNSELECTABLE attribute to
off does not ensure that the element is selectable. One example is anHTMLApplication (HTA) with the
SELECTION attributeset to
no. Elements in the body of theHTA cannotbe selected, whether the element's
UNSELECTABLE attribute is setto
off or not.
Clicking on an element with the
UNSELECTABLE attribute set to
on does not destroy the current selection if one exists.
An element with the
UNSELECTABLE attribute set to
oncanbe included in a selection that starts somewhere outside the element.
The UNSELECTABLE attribute is implemented as anexpando.Setting the
expando property of the
document object to
false precludes the functionality of allexpandos.
#14楼 回复 引用 查看
有一些地方还是不明白,比如我在这里定义的一个类(另外一篇的singleton类),我想只罗列出它的属性,
function Singleton(){
if(this.constructor.instance){
return this.constructor.instance;
}else{
this.constructor.instance = this;
}
this.value = parseInt(Math.random()*100000);
}
Singleton.prototype.getValue = function(){
return this.value;
}
Singleton.prototype.setValue = function(value){
this.value = value;
}
Singleton.prototype.toString = function(){
return "Singleton Class";
}
采用上面最初的function getExpandoValues(map){...}方法还是会把我所定义的getValue和setValue打印出来,我看了你的这么多篇关于prototype的文章也没明白到底是个什么东西-_-,但是为什么不能做这样的修改呢?
function getExpandoValues(map){
var values = [];
var obj = new map.constructor();
for ( var key in map ){
if(typeof(map[key])!="function"){
values.push(map[key]);
}
}
return values;
}
只有不是function类型的才加到数组里,这样显示的结果就只有我想要的value一个,
我加你msn和qq都没反应。。。。
msn:
[email protected]
#15楼 [楼主] 回复 引用 查看
@long_biti
我这个方法的目的是取出所有的expando属性,而你的那个setValue和getValue虽然是function类型,但是这两个方法对于其所属对象来说,也是expando属性。
你要滤掉function只是一个特殊需求,完全可以像你那样来修改,不过修改由就不是获得的所有expando了。
#16楼 回复 引用 查看
楼主的意思是要在for in时不包括prototype已有的东西吧?
那最直接的方式就是:
for (var x in o)
if (o.hasOwnProperty(x))
... // x 不在 prototype 中
#17楼 回复 引用 查看
就是用这个:hasOwnProperty
#18楼 [楼主] 回复 引用 查看
@5678
yeah, 就是这个,3ks。用hasOwnProperty确实更专业些:)
function
GetExpandoValues(map)
{
var values = [];
for ( var key in map )
{
if ( map.hasOwnProperty(key) )
{
values.push(map[key]);
}
}
return values;
}