sort和Localecompare的经历

从一个bug讲起

最近收到个用户反馈,在某个功能的列表上希望有一定顺序,而不是看起来有点像乱序的感觉。

看了一下代码,前人的写法是后端按照数据库默认的顺序(通常下是创建时间)返回的数组做字典转换,然后用Object.keys 转成数组(这里已经乱了),加上sort里传localecompare的值进行对比,如果只是数字英文字母倒好说,但实际情况是, 还有中文,中文的顺序就更加扑朔迷离了。

先看sort本身看

sort()方法用原地算法对数组的元素进行排序,并返回数组。默认排序顺序是在将元素转换为字符串,然后比较它们的UTF-16代码单元值序列时构建的

所以按照这个原因,数字在参与排序的时候,肯定不是按照人本能以为的顺序排

比如

const months = ['March', 'Jan', 'Feb', 'Dec'];
months.sort();
console.log(months);
// expected output: Array ["Dec", "Feb", "Jan", "March"]

const array1 = [1, 30, 4, 21, 100000];
array1.sort();
console.log(array1);
// expected output: Array [1, 100000, 21, 30, 4]

深入看一下这个10000怎么排到前面的

[1, 30, 4, 21, 100000]

每个字符codeCodeAt一下

[[49],[51,48],[52],[50,51],[49,48,48,48,48]]

显而易见

两个49就排到前面了。

如果第一位相等,再看后面几位的了

[1000, 1, 100, 10000, 10].sort()
// [1, 10, 100, 1000, 10000]


const fruit = ['cherries', 'apples', 'bananas'];
fruit.sort();
// ['apples', 'bananas', 'cherries']

可以看到英文字符的排序都是按照首字母排序。

const numbers = [3, 2, 1];
numbers.sort();
// [1, 2, 3]

数字也是一样

const words = ['测图', '案头', '不投'];
words.sort();
// ["不投", "案头", "测图"]

可以看到中文并不是按照拼音排序的。

['牛','a', '阿尔','b', '木头', 3,'c', 2, 1 ].sort()
// [1, 2, 3, "a", "b", "c", "木头", "牛", "阿尔"]

所以一般情况下,如果列表只需要排数字在前,其次英文,再是中文的时候,其实默认的sort已经能满足需求了,当然不包括一些字符,部分字符在英文后中文前,有些在中文后。


但通常中文产品里,需求会希望中文能按照拼音排序,那这个时候就引入localecompare。

['测图', '案头', '不投'].sort((a,b)=>a.localeCompare(b))
// ["案头", "不投", "测图"]

可以看到确实达到了我们的需求。

看看其他情况

['级别2-2', '级别1-1', '级别2-1'].sort((a,b)=>a.localeCompare(b))
// ["级别1-1", "级别2-1", "级别2-2"]

还能排序音调,比如

['逆', '你', '妮', '拟'].sort((a,b)=>a.localeCompare(b))
// ["妮", "你", "拟", "逆"]


看起来像这样的中文数字组合也达到了我们的要求,但会有这样的情况

['级别2-2', '级别1-1', '级别2-1','级别11-1'].sort((a,b)=>a.localeCompare(b))
// ["级别1-1", "级别11-1", "级别2-1", "级别2-2"]

级别11排到了级别2的前面,从之前的算法来看确实没问题,但不符合产品需求了。

还有别的情况

['牛','a', '阿尔','b', '木头', '3','c', '2', '1' ].sort((a,b)=>a.localeCompare(b))
// ["1", "2", "3", "阿尔", "木头", "牛", "a", "b", "c"]

用刚才的例子,加入了localeCompare之后发现中文跑到了英文前面。

首先要知道localeCompare有第二个参数,第二个参数是叫locales是让你选择语言包,在中文体系里就是'zh'或者用更早期的简体中文'gb2312'。


我们先把数字和英文放前面

const isLetterOrNumberReg = /[0-9a-z]+/ig;
if (isLetterOrNumberReg.test(a) && !isLetterOrNumberReg.test(b)) {
  return -1;
}

然后其他的按照自己默认的语言走

['测图', 'a','案头','d', '不投', '3','c', '2', '1' ].sort((a,b)=>{
const isLetterOrNumberReg = /[0-9a-z]+/i;
if (isLetterOrNumberReg.test(a) && !isLetterOrNumberReg.test(b)) {
  return -1;
}
if (!isLetterOrNumberReg.test(a) && isLetterOrNumberReg.test(b)) {
  return 1;
}
return a.localeCompare(b);
})
// ["1", "2", "3", "a", "c", "d", "案头", "不投", "测图"]

这个时候其实已经满足了产品需求,只希望按照首字母的顺序排就好了,但还有问题就是如果其中一个项是中英文怎么办?

后面就需要通过正则按照实际使用场景来区别出希望的顺序了。



花絮

在年轻的时候使用c语言的经验,以为sort函数中只要返回true/false就好了,虽然在chrome中问题不大,虽然可能顺序不太对,但safari却会报错。


其余参考文章:

imweb.io/topic/565cf725

javascript Array.prototype.sort 排序浅谈

juejin.im/post/5caea379

发布于 2020-03-28 16:57