Object.keys()引发的血案
# 背景
接到一个需求,根据后端返回的一个对象类型的数据(非常庞大,成百上千条数据),按照标签指定的顺序,去显示和对象属性相应的标签及对应值,数据形式如下。
后端返回的的数据
data = {
100: 'Apple',
101: '5SE',
102: '16G'
}
对应的标签(放在本地的资源文件)
export const tags = {
101: '型号',
100: '厂商',
102: '内存'
}
产出的结果
型号:5SE
厂商:Apple
内存:16G
# 尝试Object.keys()
机智的我看了看数据,一拍脑门,这不简单吗,直接去遍历资源文件tags对象的属性不就可以了,so easy~
实现一:
import tags from '@/assets/tags'
// 获取key
const keys = Object.keys(tags)
获取tag和对应的值
<div v-for="(key, index) in keys">
{{tags[key]}}: {{data[key]}}
</div>
实现二
<div v-for="(val, key, index) in tags">
{{val}}: {{data[key]}}
</div>
就在我美滋滋的时候,测试反馈显示的顺序不是指定顺序。仔细看了看发现属性竟然是按照从小到大的顺序排列的,是的,属性被排序了,而且是按照从小到大。
# 探索Object.keys()/Object.getOwnPropertyNames()/Reflect.ownKeys()
# Object.keys()
经过查阅资料,发现Object.keys(obj)以数组形式,返回对象所有可枚举属性。顺序遵循for...in遍历对象返回的顺序,再看for...in。
# for...in
以任意顺序返回对象的可枚举属性。为什么是任意顺序?其顺序依赖于浏览器实现。
...Because the order of iteration is implementation-dependent, iterating over an array may not visit elements in a consistent order.
# for...in遍历对象属性顺序
- 如果属性名的类型是Number,那么Object.keys返回值是按照key从小到大排序
- 如果属性名的类型是String,那么Object.keys返回值是按照属性被创建的时间升序排序
- 如果属性名的类型是Symbol,那么逻辑同String相同
这解释了为什么我遍历出来的顺序总是从小到大排列的。参考EcmaScript规范
# 其他方法
有文章提到以下两个方法会保证对象属性的顺序,查询资料发现并不是如此,Object.getOwnPropertyNames
和Reflect.ownKeys
。但是查看文档提示如下:
Object.getOwnPropertyNames
: 数组中枚举属性的顺序与通过for...in
循环(或Object.keys
)迭代该对象属性时一致。数组中不可枚举属性的顺序未定义。Reflect.ownKeys
:它的返回值等同于Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target))
。
# 如何解决?
# 方式一
然后给出了建议
如果你想让对象的遍历顺序兼容所有的浏览器,那么你可以使用两个数组来模拟 (一个做为keys,一个做为 values), 或者建立一个由单一属性的对象组成的数组等。
# 方式二
使用ES6的Map()
类型代替,也是一种类似于对象的键值对形式。
- 不限键的类型。
- Map()结构原生提供返回键名的遍历器
Map.prototype.keys()
,并且是按照插入时的顺序返回。