字符计数

前端工程师需要了解的 Unicode 知识

不超过 20 个字符

str.length <= 20
"😂".length

String.prototype.length 是如何定义的?

The String type is the set of all ordered sequences of zero or more 16-bit unsigned integer values (“elements”) up to a maximum length of 253 - 1 elements.

——6.1.4 The String Type

The number of elements in the String value represented by this String object.

——21.1.4.1 length

length 属性表示 16bit 单元的个数

为什么有些字符占用 16bit

而有些字符占用 32bit

UTF-16 编码与代理对

Unicode 目前共定义了 0x10FFFF 个码点。其中 0x0000 - 0xFFFF 收录了各国语言中最常用的文字和符号,称为基本多语种平面

0x010000 - 0x10FFFF 称为扩展平面,用来收录使用频率较低的字符。

Unicode字符平面映射

UTF-16 是一种变长编码。对于基本多语种平面内的码点使用 16 位表示,而对于扩展平面内的码点则需要通过代理对技术使用 32 位来表示。

代理对技术是使用基本多文种平面中预留的 2048 个码位(0xD800 - 0xDFFF),通过 2 个 16bit 位置来表示一个位于扩展平面中字符的技术。

2048 个码位中 0xD800 - 0xDBFF 表示代理对高位,0xDC00 - 0xDFFF 表示代理对低位

转换规则


          // 码点转代理对
          高位 = Math.floor((码点 - 0x010000) / 1024) + 0xD800
          低位 = (码点 - 0x010000) % 1024 + 0xDC00
        

          // 代理对转码点
          码点 = (高位 - 0xD800) * 1024 + (低位 - 0xDC00) + 0x010000
        

JavaScript 中获得字符串的 Unicode 字符数


          function countUnicodeCharacters (str) {
            let result = 0;
            for (let i = 0; i < str.length; i++) {
              const charCode = str.charCodeAt(i);
              if (charCode < 0xDC00 || charCode > 0xDFFF) {
                result ++
              }
            }
            return result;
          }
        

太麻烦?

ECMAScript 2015 String.prototype[@@iterator]()

[...str].length <= 20
了吗?

一个可见字符就对应一个码点吗?

[..."café"].length
[..."café"].length

vs.

[..."café"].length

组合字符

é(U+00e9) = e(U+0065) + ´(U+0301)

Unicode 等价性

两个不同的 Unicode 序列表达了相同的含义,我们就说它们是等价的。

Unicode 正规化

将彼此等价的序列转成同一列序的操作被称为 Unicode 正规化(Unicode Normalize)。

正规化得到的序列在 Unicode 标准中称作正规形式。

ES 2015+ 中可以使用 String.prototype.normalize() 方法。

正规形式

  • NFD:标准等价分解模式:用尽可能多的字节(byte)来代表字符。
  • NFC:标准等价组合模式:用尽可能少的字节(byte)来代表字符。

DEMO

序列 café 的正规形式

[...str.normalize()].length <= 20
还没
[..."👨‍👩‍👦".normalize()].length

零宽连字符(ZWJ)

👨‍👩‍👦 = 👨(U+1F468) + [ZWJ](U+200D) + 👩(U+1F469) + [ZWJ](U+200D) + 👦(U+1F466)
[..."👍🏻👍🏼👍🏽👍🏾👍🏿".normalize()].length

肤色修饰符

👍🏿 = 👍(U+1F44D) + [Emoji Modifier Fitzpatrick Type-6](U+1F3FF)

🤦‍♂️

其实都是 JavaScript 的锅

Python


          len("🤣")
          // 1

          len("👨‍👩‍👦")
          // 5
        

Swift


          "🤣".count
          // 1

          "👨‍👩‍👦".count
          // 1