JS 判断带 emoji 的字符串长度的究极方法

JS 判断带 emoji 的字符串长度的究极方法

羽叶
2022-03-11 / 1 评论 / 52 阅读 / 正在检测是否收录...

历史的坑

JS 的字符串长度是个奇怪的设定,很多编程语言,获取字符串的长度是得到 字节长度,比如一个正常的汉字是两个字节,但在 js 中,'汉'.length 是 1 。看上去很方便,殊不知,这个特性埋下的坑。

比如:
😀 : '😀'.length 得到的是 2
𠮷 : '𠮷''.length 得到的也是 2

JS 内部字符以 UTF-16 的格式储存 (一种兼容 utf-8,但可储存编码比utf-88位),简单点说,就是最多存 16 位的编码,但中华文化博大精深,字太多了,有些字排到后面,都不只 16 位编码了,于是,在 js 中只能用 2 个 utf-16 来储存,于是,这个𠮷这就被 js 当成长度为 2 了。

生辟字很少见的,但 emoji 不少见啊,还很流行,关键的是,大多都是 编码超过 16 位,也就是说 js 中长度不止为 1 可能有的 emoji 长度为 2 都不止。

比如这个 👨‍👩‍👧‍👧 ,运行 '👨‍👩‍👧‍👧'.length 在 js 中 答案为 11 , 这里不去深究 Emoji 的历史了,有兴趣可以去了解下。

解决办法

那在需求中,比如评论输入框中 限定 20 的字符, 很明显 直接用 length 判断长度 ,用户两个 emoji 就不够了,显然不能满足需求

其他一些 for ... of codePointAt 方法在一些情况下是可以解决长度问题,但在 组合 emoji 中就无能为力了

弊端的实现

function length(s) {
  let len = 0;
  for (let i = 0; i < s.length; i++) {
    len++;
    // 0xffff 是16进制,一个f相当4位二进制,4个f就是16位
    if (s.codePointAt(i) > 0xffff) i++;
  }
  return len;
}
length("😀"); //1
length("𠮷"); //1

这个方法一些情况可以获取真实长度,但是在 组合 emoji 比如 👨‍👩‍👧‍👧 就会有问题。

length("👨‍👩‍👧‍👧"); // 7 很明显不对

1646964545534.png

究极解决

这里需要引入 lodash 的 toArray 方法 转成数组 再获取数组的长度

有兴趣的可以去 https://github.com/lodash/lodash/blob/master/toArray.js 源码了解它的实现 这里先不去深究了

function trueLength(str) {
  return _.toArray(str).length;
}

1646964253773.png

不想引入loadsh 我这边根据源码重新写了一个

const countStrLen  = (string) => {
  const reUnicode = /\ud83c[\udffb-\udfff](?=\ud83c[\udffb-\udfff])|(?:[^\ud800-\udfff][\u0300-\u036f\ufe20-\ufe2f\u20d0-\u20ff\u1ab0-\u1aff\u1dc0-\u1dff]?|[\u0300-\u036f\ufe20-\ufe2f\u20d0-\u20ff\u1ab0-\u1aff\u1dc0-\u1dff]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff]|[\ud800-\udfff])[\ufe0e\ufe0f]?(?:[\u0300-\u036f\ufe20-\ufe2f\u20d0-\u20ff\u1ab0-\u1aff\u1dc0-\u1dff]|\ud83c[\udffb-\udfff])?(?:\u200d(?:[^\ud800-\udfff]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff])[\ufe0e\ufe0f]?(?:[\u0300-\u036f\ufe20-\ufe2f\u20d0-\u20ff\u1ab0-\u1aff\u1dc0-\u1dff]|\ud83c[\udffb-\udfff])?)*/g;
  return (string.match(reUnicode) || []).length
}
countStrLen('👨‍👩‍👧‍👧') //1

或者直接挂载在原型链上

String.prototype.countLengtn = function() {
  const reUnicode = /\ud83c[\udffb-\udfff](?=\ud83c[\udffb-\udfff])|(?:[^\ud800-\udfff][\u0300-\u036f\ufe20-\ufe2f\u20d0-\u20ff\u1ab0-\u1aff\u1dc0-\u1dff]?|[\u0300-\u036f\ufe20-\ufe2f\u20d0-\u20ff\u1ab0-\u1aff\u1dc0-\u1dff]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff]|[\ud800-\udfff])[\ufe0e\ufe0f]?(?:[\u0300-\u036f\ufe20-\ufe2f\u20d0-\u20ff\u1ab0-\u1aff\u1dc0-\u1dff]|\ud83c[\udffb-\udfff])?(?:\u200d(?:[^\ud800-\udfff]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff])[\ufe0e\ufe0f]?(?:[\u0300-\u036f\ufe20-\ufe2f\u20d0-\u20ff\u1ab0-\u1aff\u1dc0-\u1dff]|\ud83c[\udffb-\udfff])?)*/g;
  return (this.toString().match(reUnicode) || []).length
}
"𠮷👨‍👩‍👧‍👧👨‍👩‍👧‍👧😀𠮷".countLengtn() // 5

1646986129528.png

2

评论 (1)

取消
  1. 头像
    上海seo
    Windows 10 · Google Chrome

    不错的知识点,谢谢分享呢。。

    回复