iview组件库table诡异排序

iview组件库table诡异排序
前几天,老大反馈说渠道营销后台的表格排序很诡异,到底什么诡异的现象呢?这里先来看一下
无排序正常效果

”登录成本“ 降序排列

“登录成本” 升序排列

很奇怪,无论是升序排列还是降序排列,登录成本中的两个0记录都会在中间出现。难道iview的表格排序内部做了什么额外的处理吗?带着这个疑问,去看一下iview的table组件是怎么处理表格排序的?
iview中的表格排序方法通过table组件的sortData方法实现的。下面是它的源码
sortData (data, type, index) {
const key = this.cloneColumns[index].key;
data.sort((a, b) => {
if (this.cloneColumns[index].sortMethod) {
return this.cloneColumns[index].sortMethod(a[key], b[key], type);
} else {
if (type === ‘asc’) {
return a[key] > b[key] ? 1 : -1;
} else if (type === ‘desc’) {
return a[key] < b[key] ? 1 : -1;
}
}
});
return data;
}
源码中对表格数据的排序就是通过Array.prototype.sort方法实现的。在我们的这个场景中,我们没有自定义排序方法sortMethod,所以sort的回掉函数会始终走else分支。在这个分支里,会根据我们的排序方式选择不同的分支去处理。为了验证一下这个排序方法的准确性,我写了如下代码
const array = [27.47, 21.98, 0, 28.27, 20.66, 0, 17.22] // 上述表格中登录成本 显示的数据

console.log(
array.sort((a, b) => {
return a < b ? 1 : -1
})
)
然后使用node运行一下,得到如下结果
// [ 28.27, 27.47, 21.98, 20.66, 17.22, 0, 0 ]
而且和浏览器中运行的结果一致。
很懵逼,为什么这里排序准确无误,表格中的排序那么乱呢?难道是我们的数据有问题?看了一下接口返回,突然意识过来,我们用在表格中排序的data,并不是由数字组成的数组,而是对象数组。下面是精简后的数据
const array = [
{ “name”: “应用商店组”, “count”: 9494, “cost”: 27.47, },
{ “name”: “精准营销组”, “count”: 10562, “cost”: 21.98, },
{ “name”: “其他”, “count”: 10696, },
{ “name”: “广告媒体组”, “count”: 3148, “cost”: 28.27, },
{ “name”: “社交媒体组”, “count”: 1241, “cost”: 20.66, },
{ “name”: “渠道合作”, “count”: 148, },
{ “name”: “合计”, “count”: 35289, “cost”: 17.22, }
]
那我们就对这段数据以cost(表格中的登录成本的key)的值进行一个排序,
console.log(
array.sort((a, b) => {
return a.cost < b.cost ? 1 : -1
})
)
在nodejs运行结果如下
[ { name: ‘应用商店组’, count: 9494, cost: 27.47 },
{ name: ‘精准营销组’, count: 10562, cost: 21.98 },
{ name: ‘其他’, count: 10696 },
{ name: ‘广告媒体组’, count: 3148, cost: 28.27 },
{ name: ‘社交媒体组’, count: 1241, cost: 20.66 },
{ name: ‘渠道合作’, count: 148 },
{ name: ‘合计’, count: 35289, cost: 17.22 } ]
果然,数据并不是严格的按照cost的值降序排列。我们仔细观察一下原数据,你会发现name为其它和渠道合作的两个元素,并没有cost字段。所以,这两个元素以cost列进行排序的时候,其在sort回掉函数中获得的值是undefined。undefined和任何值进行比较,结果都为false,即
undefined > 100 // false
undefined === 100 // false
undefined < 100 // false
所以这两列元素在进行比较的时候sort方法的回掉函数返回值都是-1。过程清楚了,但是为什么输出上面的结果,还不是很清楚。带着疑问,就去看看Array.prototype.sort方法是怎么实现的?
经查资料得知,V8对于数组的排序实现了两种排序算法:插入排序和快速排序。这一点,我们在V8仓库中也能看出
我的版本是8.10对应的v8版本是6.2.414.50

// For short (length <= 10) arrays, insertion sort is used for efficiency.
源码中明确说明了,当数组长度小于10的时候使用插入排序。下面是V8的插入排序算法
function InsertionSort(a, from, to) {
for (var i = from + 1; i < to; i++) {
var element = a[i];
for (var j = i – 1; j >= from; j–) {
var tmp = a[j];
var order = comparefn(tmp, element);
if (order > 0) {
a[j + 1] = tmp;
} else {
break;
}
}
a[j + 1] = element;
}
};
参数a就是待排序的数组,from就是开始位置,to就是结束的位置。comparefn就是我们传入给sort方法的回掉函数。对于我们这种场景,我们可以稍微作一下改动就能验证结果了
const comparefn = (a, b) => {
return a.cost < b.cost ? 1 : -1
}

function InsertionSort(a, from, to) {
for (var i = from + 1; i < to; i++) {
var element = a[i];
for (var j = i – 1; j >= from; j–) {
var tmp = a[j];
var order = comparefn(tmp, element);
if (order > 0) {
a[j + 1] = tmp;
} else {
break;
}
}
a[j + 1] = element;
}
};
然后对我们上面的数组进行排序
InsertionSort(array, 0, array.length)
果然,结果和Array.prototype.sort的排序一致
[ { name: ‘应用商店组’, count: 9494, cost: 27.47 },
{ name: ‘精准营销组’, count: 10562, cost: 21.98 },
{ name: ‘其他’, count: 10696 },
{ name: ‘广告媒体组’, count: 3148, cost: 28.27 },
{ name: ‘社交媒体组’, count: 1241, cost: 20.66 },
{ name: ‘渠道合作’, count: 148 },
{ name: ‘合计’, count: 35289, cost: 17.22 } ]
上面排序算法关键之处就在于order < 0的时候,最内部的循环直接break了。回到上面我们说的undefined和任何值相比较都是false,sort回掉函数的返回值永远是-1。当进行name是其他和渠道合作两个元素的排序的时候,InsertionSort函数内部的第二层for循环进去就出来了,所以,对于这两列而言,排序之后的位置永远不变。
但是,还没结束,把下面的代码在浏览器中运行一下
console.log(
array.sort((a, b) => {
return a.cost < b.cost ? 1 : -1
})
)
结果是这样的
[ { name: ‘渠道合作’, count: 148 },
{ name: ‘广告媒体组’, count: 3148, cost: 28.27 },
{ name: ‘其他’, count: 10696 },
{ name: ‘应用商店组’, count: 9494, cost: 27.47 },
{ name: ‘精准营销组’, count: 10562, cost: 21.98 },
{ name: ‘社交媒体组’, count: 1241, cost: 20.66 },
{ name: ‘合计’, count: 35289, cost: 17.22 } ]
这个结果和最初一样表格中的排序结果是一样的。但是,很奇怪,难道浏览器端的Array.prototype.sort方法不是V8中实现的吗,还是版本的不同造成了结果的差异?
看一下我的chrome的版本吧:chrome@72.0.3626,对应的V8@7.2.502。那就到V8的仓库中看一下这个版本的sort方法的实现。https://github.com/v8/v8/blob/7.2.502/src/js/array.js#L239
你会发现V8@7.2.502和V8@6.2.414的实现方法是一样的,但为什么结果确是不一样的呢?然后在V8@7.2.502全局搜索了一下sort关键字,也没发现其它排序方法的实现。既然找不到,就大胆猜测一下吧—难不成Chrome使用的是 二分插入排序?然后,扒了一段二分插入排序算法的实现(记不住啊记不住)
function binaryInsertSort(arr) {
for (var i = 1; i < arr.length; i++) {
var key = arr[i],
left = 0,
right = i – 1;
while (left <= right) {
var middle = parseInt((left + right) / 2);
if (key.totalLoginCost < arr[middle].totalLoginCost) {
right = middle – 1;
} else {
left = middle + 1;
}
}
for (var j = i – 1; j >= left; j–) {
arr[j + 1] = arr[j];
}
arr[left] = key;
}
return arr;
}
然后在node端运行一下
console.log(
binaryInsertSort(array).reverse()
)
保持结果一致, reverse了一下
[ { name: ‘渠道合作’, count: 148 },
{ name: ‘广告媒体组’, count: 3148, cost: 28.27 },
{ name: ‘其他’, count: 10696 },
{ name: ‘应用商店组’, count: 9494, cost: 27.47 },
{ name: ‘精准营销组’, count: 10562, cost: 21.98 },
{ name: ‘社交媒体组’, count: 1241, cost: 20.66 },
{ name: ‘合计’, count: 35289, cost: 17.22 } ]
这次的输出结果就和chrome中一致了。但是chrome中实现这段排序的代码在哪里?????????????
最后呢,对于这种场景下的iview的表格排序问题,可以通过下面三种方式解决

改源码,undefined || 0
自定义sortMethod
数据传给table组件前,自己做一下判断,尽量避免undefined出现

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理