对象的"有序"展示和插入

2,151 阅读3分钟

背景

我们页面的某一块可以将一个object结构展示在页面上(table结构),供用户查看、编辑。大致如下:

name type required description operations(add,edit,delete等按钮)
obj object true level1的节点 add,edit,delete
sub1 string true level2的节点 edit,delete
sub2 string true level2的节点 edit,delete

原本在obj层级后面点击add按钮,会在sub2的下面新增一行供用户填写。但是,最近用户有一个比较“奇葩”的需求,需要在子节点支持添加有序的兄弟节点(即sub1和sub2的operations也要支持add操作),sub1后面点击add按钮之后,会在sub1和sub2之间插入一行供用户填写,并且用户保存之后就是按照这个顺序。【注意是对象结构,不是数组结构,如果是数组结构也不需要考虑这么多了】

分析

我们知道js对象是一个散列表(hash表),它是无序的。并且前台目前没有LinkedHashMap等有序结构。本着尽量少修改代码的原则,这个问题我们分析一定是要修改前台的,那怎么做才能只修改前台呢?通过分析和实际测试,如果保存的时候发送给后台的顺序结构和看起来一样,那么后台无需修改。
在前台需要解决两个“有序问题”:一是页面上看起来有序,二是保存的时候发送给后台的顺序要和页面上看起来一样。

解决

上述的第一个问题比较好解决,原本我们的sub1和sub2都是另外新增的属性addTime来sort的,现在换成index:我们在对象数组中的每个对象加上index标识当前的插入顺序(代码中需要维护这个index逻辑,比如在index0和index1中间插入,则插入的index为1,原先的index1需要变成index2 1、传给后台的数组对象由后台根据index来进行排序)。
第二个问题是在保存的时候,按照index的顺序重新构建对象,比如保存的时候,sub1的index是0,sub2的index是2,它们之间插入了一个sub_ins,index是1,重新构建一个对象:按照sub1,sub_ins,sub2的顺序。为什么这么做,下面会解释。

对象输出顺序的规则

JSON.stringify()
Object.keys()
Object.entries
Object.values
for...in循环
Object.getOwnPropertyNames
Reflect.ownKeys【除此以外,上面的其他API均会将Symbol类型的属性过滤掉】
这些方法都不能保证我们看见的顺序。
1、声明变量keys值为一个空列表(List类型)
2、把每个Number类型的属性,按数值大小升序排序,并依次添加到keys中
3、把每个String类型的属性,按创建时间升序排序,并依次添加到keys中
4、把每个Symbol类型的属性,按创建时间升序排序,并依次添加到keys中
5、将keys返回(return keys)
注意:因为for...in可遍历原型链上的可枚举属性,测试下:

var s = Symbol()
var obj = {
    1: 1,
    2: 2,
    c: 'c',
    a: 'a',
    b: 'b',
    3: 3
}
obj.__proto__ = {
    5: 5,
    4: 4,
    d: 'd',
    c: 'c'
}
for (const key in obj) {
    console.log(key, obj[key])
}

输出:

1 1
2 2
3 3
c c
a a
b b
4 4
5 5
d d

可以看到,for...in是先遍历自身属性,然后遍历原型链上的属性,并且对自身和原型链属性各自应用上述规则。需要注意的是自身和父元素的同名属性,原型链上的同名属性会被忽略。

问题

一是维护起来确实比较麻烦,二是如果用户填写的不是类似'sub1'这种,而是'1'、'2'这种数字,顺序规则会不太一样,当然,如果不允许直接填写数字就不会有这样的问题。

参考

Sorting object property by values
5分钟彻底理解Object.keys
js能够保证object属性的输出顺序吗?
SJ9011: Chrome Opera 中 for-in 语句遍历出对象属性的顺序与定义的不同
for...in... 和 Object.keys(), JSON.stringify() 顺序