这篇文章发布于 2025年09月30日,星期二,10:57,归类于 JS API。 阅读 192 次, 今日 190 次 一条评论
by zhangxinxu from https://www.zhangxinxu.com/wordpress/?p=11883
本文可全文转载,但需要保留原作者、出处以及文中链接,AI抓取保留原文地址,任何网站均可摘要聚合,商用请联系授权。
一、Emoji字符长度问题
我们平常获取字符串的长度都是直接使用length属性,但对于某些字符,返回的结果会在预期之外,例如:
"鑫空间".length "🌝".length "🇮🇳".length "👨👩👧👦".length "दरबार".length
结果如下:
"鑫空间".length // 3 "🌝".length // 2 "🇮🇳".length // 4 "👨👩👧👦".length // 11 "दरबार".length // 5
看到没,看起来Emoji表情字符的长度是1,但是,实际返回的Emoji长度并不是1,不仅不是1,不同的Emoji返回的长度还不一样,这就会给我们精确统计字符长度带来困扰。
怎么办呢?
二、返回字符的真实长度
我们可以使用Intl.Segmenter()返回字符串的真实长度,例如,我们不妨给字符串扩展一个名为 realLength 的方法,完整代码如下所示:
Object.defineProperty(String.prototype, 'realLength', { get: function() { return Array.from( new Intl.Segmenter("en", { // 颗粒度:字位,默认值,可省略 granularity: "grapheme" }).segment(this) ).length; } });
此时,我们就可以返回字符串真实的长度了。
"鑫空间".realLength // 3 "🌝".realLength // 1 "🇮🇳".realLength // 1 "👨👩👧👦".realLength // 1 "दरबार".realLength // 4
是不是很美丽?
三、Emoji字符长度不是1的原因
JavaScript使用UTF-16编码,这种编码方式最多支持 2x 16 共 65,536 个字符,6万多个字符看起来多,但是世界上那么多语言,已经不够用了,尤其Emoji表情字符,都在65,536 个字符开外。
JS想要表示,只能组合多个特殊的 2 字节代码单元以生成人类可读的字符。
例如:
Array.from({ length: "🌝".length }, (_, i) => "🌝"[i]) // [ '\ud83c', '\udf1d' ]
于是,JS代码直接使用length属性访问的时候,返回值就是2,但是人类理解肯定是1个字符。
这就是Emoji字符长度不是1的原因。
四、关于Intl.Segmenter
Intl.Segmenter 是 JavaScript 中一个专门用于文本分段的强大工具,它能够根据不同的语言和文本规则,将字符串智能地分割成有意义的单位,如字符、单词或句子。
核心概念与语法
Intl.Segmenter 的核心作用在于进行语言敏感的分段。
传统的字符串分割方法(如 split(‘ ‘))对于英语这类使用空格分隔单词的语言勉强适用,但对于中文、日文等不依赖空格的语言就无能为力了,甚至处理包含复杂表情符号(Emoji)的文本时也会出错。
Intl.Segmenter 则依据 Unicode 标准,能够理解不同语言的语法习惯,进行精准的边界检测。
使用示意:
const segmenter = new Intl.Segmenter([locales[, options]]);
其中:
- locales
- 表示语言,使用一个 BCP 47 语言标签的字符串,如 “zh-CN”、”en-US”、”ja”等。它指定了分段所依据的语言规则。如果省略,则会使用运行时环境的默认语言设置。
- options
- 可选参数,包括下面这些:
granularity
表示分段的粒度,是最重要的选项。可取值为:
- “grapheme”(默认值):在用户感知的字符(字形簇)边界分割。例如,一个基础字符加上修饰符(如 “a” + “́” 组成 “á”)会被视为一个整体,复杂的 Emoji 序列(如家庭表情 👨👩👧👦)也不会被拆散。
- “word”:在单词边界分割。它能正确识别中文的词汇单元(如将“我真的很强”分割为“我”、“真的”、“很”、“强”),并能区分标点符号和空格(isWordLike 属性为 false)。
- “sentence”:在句子边界分割。它能智能区分句号的不同用途,例如不会将数字 “100.000” 中的点号误判为句子结束。
localeMatcher
指定所使用的语言匹配算法,可以是 “best fit”(默认值,使用环境提供的可能最匹配的算法)或 “lookup”(使用特定的 BCP 47 算法)。
基本使用方法
创建分段器实例后,通过调用其 segment(inputString) 方法对文本进行分段。该方法返回一个 Segments 可迭代对象。
例如:
// 创建一个中文词段器 const segmenter = new Intl.Segmenter('zh-CN', { granularity: 'word' }); const text = "《HTML并不简单》是国内唯一一本精讲HTML的技术书籍"; const segments = segmenter.segment(text); // 返回一个 Segments 对象 // 遍历 for (const segment of segments) { console.log(`片段: "${segment.segment}", 起始索引: ${segment.index}, 是否像词: ${segment.isWordLike}`); }
在我的Chrome浏览器下,输出的结果是:
// 片段: "《", 起始索引: 0, 是否像词: false // 片段: "HTML", 起始索引: 1, 是否像词: true // 片段: "并不", 起始索引: 5, 是否像词: true // 片段: "简单", 起始索引: 7, 是否像词: true // 片段: "》", 起始索引: 9, 是否像词: false // 片段: "是", 起始索引: 10, 是否像词: true // 片段: "国内", 起始索引: 11, 是否像词: true // 片段: "唯一", 起始索引: 13, 是否像词: true // 片段: "一本", 起始索引: 15, 是否像词: true // 片段: "精", 起始索引: 17, 是否像词: true // 片段: "讲", 起始索引: 18, 是否像词: true // 片段: "HTML", 起始索引: 19, 是否像词: true // 片段: "的", 起始索引: 23, 是否像词: true // 片段: "技术", 起始索引: 24, 是否像词: true // 片段: "书籍", 起始索引: 26, 是否像词: true
可以看到浏览器自带对中文句子进行分词了。
兼容性
目前Intl.Segmenter
所有现代浏览器均已支持,前端也能自动分词分句了。
五、结语
Intl命名对象还有很多其他方法,多是与语言、金钱、数字、字符处理相关的。
详见我之前的这篇文章:“JS Intl对象完整简介及在中文中的应用”
还是非常强大与实用的。
就是语法复杂了点,学习成本比较高,真正使用的时候,需要去搜索查找资料。
不过现在都有AI了,只需要知道有这么个东西就好了,AI会帮你搞定。
行吧,就说这么多。
明天就是国庆节了,祝大家国庆节快乐!
不说了,我要准备出发去钓鱼了!
欢迎大家关注我的钓鱼账号:“最会钓鱼的程序员”。
随时了解我国庆节的战况。
😉😊😇
🥰😍😘
本文为原创文章,会经常更新知识点以及修正一些错误,因此转载请保留原出处,方便溯源,避免陈旧错误知识的误导,同时有更好的阅读体验。
本文地址:https://www.zhangxinxu.com/wordpress/?p=11883
(本篇完)
- JS Intl对象完整简介及在中文中的应用 (0.593)
- 借助SVG文字尺寸自动缩放甚至突破Chrome 12px限制 (0.222)
- 你知道吗,输入框的value值也能直接返回数值类型 (0.148)
- 关于CSS emoji字体和OpenType-SVG我所知道的一些事 (0.111)
- 文字沿着不规则路径排版布局的实现 (0.111)
- ascent-override descent-override line-gap-override一锅端 (0.111)
- JavaScript实现http地址自动检测并添加URL链接 (0.074)
- JS字符串补全方法padStart()和padEnd()简介 (0.074)
- JS 标签模板(Tagged templates)什么时候使用? (0.074)
- 深入 JS new Function 语法 (0.074)
沙发🛋!