[Effective JavaScript 笔记]第46条:使用数组而不要使用字典来存储有序集合

对象属性无序性

js对象是一个无序属性集合。

var obj={};
obj.a=10;
obj.b=30;

属性a和属性b并没有谁前谁后之说。for...in循环,先输出哪个属性都有可能。
获取和设置不同的属性与顺序无关,都会以大致相同的效率产生相同的结果。
也就是说访问属性a和访问属性b,没有哪个访问更快之说。ES标准并未规定属性存储的任何特定顺序,甚至于枚举对象也未涉及。for...in循环会挑选一定的顺序来枚举对象的属性,标准允许js引擎*选择一个顺序,它们的选择会微妙地改变程序行为。
如要求一个对象表示一个从字符串到值的有序映射,创建一个有序的报表。

function report(highScores){
    var res='';
    var i=1;
    for(var name in highScores){
        res+=i+'. '+highScores[name].name+':'+highScores[name].points+'
';
        i++;
    }
    return res;
}

report([{name:'张三',points:1110111},
        {name:'李四',points:1110102},
        {name:'王五',points:1110911}]);
/*预期的结果
"1. 张三:1110111
2. 李四:1110102
3. 王五:1110911
"
*//*实际的结果 chrome,ff,ie
"1. 张三:1110111
2. 李四:1110102
3. 王五:1110911
"
*/

上面代码在测试的几个环境中表现顺序和索引相符,但一些其它的环境可以选择以不同的顺序来存储和枚举对象的属性,所以report有可能导致产生不同的字符串,得到不正确的报表。
程序对对象枚举的顺序依赖并不是显式地。如果没有在多个js环境中测试过你的代码,那么你的程序有可能因为for...in循环的不同输出而导致改变。

依赖数据顺序

如果对于数据结构中的条目顺序有强依赖,那么就优先考虑数组而不是字典。如上面的report函数如果接收的是一个数组而不是一个对象,那么可以用for来循环,可以保证在所有环境顺序都是一致正确的。

function report(highScores){
    var res='';
    for(var i=0,n=highScores.length;i < n;i++){
        var score=highScores[i];
        res+=(i+1)+'. '+score.name+':'+score.points+'
';
    }
    return res;
}

report([{name:'张三',points:1110111},
        {name:'李四',points:1110102},
        {name:'王五',points:1110911}]);
//"1. 张三:1110111
2. 李四:1110102
3. 王五:1110911
"

通过接收一个对象数组,每个对象包含有name和points属性,上面的代码可以按0~highScores.length-1的顺序遍历所有的元素。

浮点型运算

假设有一个映射标题和等级的电影字典。

var ratings={
    'Good Will Hunting':0.8,
    'Mystic River':0.7,
    '21':0.6,
    'Doubt':0.9
};

浮点型算术运算的四舍五入会导致对计算顺序依赖。详细见《第2条:理解JavaScript的浮点数》。当组合未定义顺序的枚举时,可能会导致循环不可预知。

var total=0,count=0;
for(var key in ratings){
    total+=ratings[key];
    count++;
}
total/=count;
total;//chrome里:0.7499999999999999

在流行的js环境实际上使用不同的顺序执行这个循环。一些环境按照下面的顺序来枚举对象的key,得到下面这个值。

(0.8+0.7+0.6+0.9)/4 //0.75

有些环境总是先枚举潜在的数组索引,然后才是其他key。电影21是可以作为数组的索引的整数值,它首先被枚举,得到下面的结果。

(0.6+0.8+0.7+0.9)/4 //0.7499999999999999

可以看到上面的chrome就是先枚举潜在的数组索引。

整数计算浮点型

对于浮点数的计算,可以把浮点数转化为整数,然后再转化回浮点数。整数的计算顺序可以是任意顺序的。所以对象的属性值的列举顺序并不重要。代码如下

(8+7+6+9)/4/10 //0.75
(6+8+7+9)/4/10 //0.75

提示

  • 使用for...in循环来枚举对象属性应当与顺序无关

  • 如果聚集运算字典中的数据,确保聚集操作与顺序无关

  • 使用数组而不是字典来存储有序集合