前端按姓名汉字A-Z排序

1,239 阅读4分钟

提出问题,哪些需求的列表需要按A-Z进行排序

在群聊相关的群成员列表、通讯录等需要按照人的姓名或者昵称进行排序,不同于后台大多是按照时间进行排序。

排序规则(群聊姓名和昵称中包括汉字、英文字母、数字以及特殊符号),例:

  1. 按照姓名或者昵称进行排序,优先按照群昵称进行排序,如果没有群昵称,则取姓名进行排序
  2. 按照首字母按A-Z进行排序,如首字母相同,则比较第2个字母,依次往后....(群主和管理员要排到列表最前面)
  3. 汉字需要按照汉语拼音同英文进行比较排序
  4. 特殊符号和数字排列到所有列表的最后(根据不同的需求也可能会将数字按从小到大进行排序)

实现过程及思路

  1.   首先查找关于js的sort()排序:

        sort() 方法*就地*对数组的元素进行排序,并返回对相同数组的引用。默认排序是将元素转换为字符串,然后按照它们的 UTF-16 码元值升序排序。

        看上去是按照A—Z进行排序的,但是加上小写字母和汉字之后不能按要求排序:

        总结下来,sort()基本的排序规则按照UTF-16 码元值升序排序,UTF-16 编码单元匹配能用一个 UTF-16 编码单元表示的 Unicode 码点,即String.prototype.charCodeAt()返回的值按从小到大进行排序:

        charCodeAt() 方法返回 065535 之间的整数,表示给定索引处的 UTF-16 代码单元,例如:

    'a'.charCodeAt() // 97
    'z'.charCodeAt() // 122
    'A'.charCodeAt() // 65
    'Z'.charCodeAt() //90
    '啊'.charCodeAt() // 21834
    

        这就不难理解,为啥小写‘a’和中文‘啊’会在排序后数组的最后面。

  1.   解决大小写 Unicode码不同的问题

    • 第一种方法是将所有的所有的大小写字母统一转为大写字母或者小写字母:

      •       String.prototype.toLowerCase() 将字符转为小写字母
      •       String.prototype.toUpperCase() 将字符转为大写字母
    • 第二种方法是使用String.prototype.localeCompare() Api来比较:

        基本用法:'B'.localeCompare('c') ,如果前者大于后者则返回-1,否则返回1,相等则返回0

    'B'.localeCompare('c') // -1
    'A'.localeCompare('B') // -1
    'A'.localeCompare('b') // -1
    'A'.localeCompare('A') // 0
    'C'.localeCompare('a') // 1
    
  1.   汉字转成拼音

        通过pinyin-pro 这个库来实现汉字转拼音:

    // 获取字符串格式拼音
    pinyin('汉语拼音'); // 'hàn yǔ pīn yīn'
    // 获取数组格式拼音
    pinyin('汉语拼音', { type: 'array' }); // ["hàn", "yǔ", "pīn", "yīn"]
    // 获取不带音调数组格式拼音
    pinyin('汉语拼音', { toneType: 'none' }); // "han yu pin yin"
    // 获取不带音调数组格式拼音
    pinyin('汉语拼音', { toneType: 'none', type: 'array' }); // ["han", "yu", "pin", "yin"]
    // 音调以数组形式显示
    pinyin('汉语拼音', { toneType: 'num' }); // "han4 yu3 pin1 yin1"
    // 自动识别多音字
    pinyin('睡着了'); // "shuì zháo le"
    

        其原理主要是其拥有一个强的的汉字字典来进行匹配,并返回汉语拼音。

最后实现

实现整体思路:

  • 首先按群主、管理员、群成员进行排序
  • 获取群昵称或者姓名,没有群昵称的取姓名
  • 将姓名或者群昵称转为汉语拼音
  • 正则匹配特殊字符和数字并排到最后面
  • 并通过localeCompare()方法进行比较
import { pinyin } from 'pinyin-pro';
// nameCard为群昵称,nick为姓名
// owner:群主 Admin:管理员 Member:普通成员
const users = [
    { nick: "张三", role: "Member", nameCard: "张三" },
    { nick: "李四", role: "Owner", nameCard: "李四" },
    { nick: "t川", role: "Admin", nameCard: "t川" },
    { nick: "王五", role: "Member", nameCard: "王五" },
    { nick: "AAA", role: "Admin", nameCard: "AAA" },
    { nick: "###", role: "Owner", nameCard: "##3" },
    { nick: "111", role: "Member", nameCard: "111" },
    { nick: "。。", role: "Admin", nameCard: "。。。。" }
 ];

// 定义排序函数
const customSort = (a, b) => {
  const roleOrder = { Owner: 0, Admin: 1, Member: 2 };

  // 按照 role 排序
  if (a.role !== b.role) {
    return roleOrder[a.role] - roleOrder[b.role];
  }

  // 对于相同 role 的元素,按照 nameCard 或 nick 转成汉语拼音
  const aPinyin = pinyin(a.nameCard || a.nick, { toneType: 'none' }).replace(/\s/g, '');
  const bPinyin = pinyin(b.nameCard || b.nick, { toneType: 'none' }).replace(/\s/g, '');
  
  // 判断 a 和 b 是否为特殊符号或数字
  const isSpecialA = /[\W\d]/.test(aPinyin[0]);
  const isSpecialB = /[\W\d]/.test(bPinyin[0]);

  // 如果 a 和 b 都是特殊符号或数字,按原始顺序排序
  if (isSpecialA && isSpecialB) {
      return 0;
  }

  // 如果 a 是特殊符号或数字,将其排在 b 后面
  if (isSpecialA) {
      return 1;
  }

  // 如果 b 是特殊符号或数字,将其排在 a 后面
  if (isSpecialB) {
      return -1;
   }

  return aPinyin.localeCompare(bPinyin);
};

// 执行排序
users.sort(customSort);

// 输出结果
console.log(users);
// [
//   { nick: '李四', role: 'Owner', nameCard: '李四' },
//   { nick: '###', role: 'Owner', nameCard: '##3' },
//   { nick: 'AAA', role: 'Admin', nameCard: 'AAA' },
//   { nick: 't川', role: 'Admin', nameCard: 't川' },
//   { nick: '。。', role: 'Admin', nameCard: '。。。。' },
//   { nick: '王五', role: 'Member', nameCard: '王五' },
//   { nick: '张三', role: 'Member', nameCard: '张三' },
//   { nick: '111', role: 'Member', nameCard: '111' }
// ]