You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

为何Array.from处理字符串的结果与.split('')存在差异?

为何Array.from处理字符串的结果与.split('')存在差异?

这个问题的核心在于**split('')Array.from对字符串的拆分逻辑完全不同**——一个按UTF-16代码单元拆,一个按实际的Unicode字符(码点)拆,咱们结合你的例子一步步说:

首先看你的测试字符串:

str = `奈樂❤️藍`;
// str.length = 24
// str.split("") = [ "\ud83d", "\udc4d", /* 22 more elements */ ],

你发现str.length是24,这是因为JavaScript字符串底层用UTF-16编码存储:非BMP的字符(比如那些表情)需要2个16位的代码单元(代理对)来表示,而中文、普通符号只需要1个。所以这个字符串实际包含13个完整字符,但占了24个UTF-16代码单元,因此str.length显示为24。

先看str.split('')的行为

split('')的逻辑很直接:把字符串按单个UTF-16代码单元拆分,不管这个单元是不是一个完整的字符。所以像""这种由\ud83d\udc4d两个代码单元组成的表情,会被拆成两个独立的数组元素,最终数组长度等于str.length(24),和你最初的预期一致。

你对Array.from的误解

你原本以为Array.from(str)会基于字符串的length,把每个UTF-16代码单元依次塞进数组,就像模拟的这段代码:

arr = Array.from({
  length: 24,
  0: "\ud83d", 1: "\udc4d" /* ... and so on */
})

但实际上Array.from的处理逻辑完全不是这样!

真实的Array.from行为

Array.from会利用字符串的可迭代特性——字符串默认实现了Symbol.iterator接口,这个迭代器是按**Unicode码点(即完整的字符)**来遍历的:

  • 对于用1个UTF-16代码单元表示的字符(比如"奈"、"樂"),迭代器直接返回这个字符;
  • 对于用2个代码单元表示的非BMP字符(比如""),迭代器会把这两个代码单元合并成一个完整的字符返回;
  • ❤️这种由(U+2764)和(U+FE0F,变体选择符)两个独立码点组成的组合,迭代器会把它们拆成两个元素返回(因为U+FE0F是单独的码点)。

所以最终Array.from(str)得到的数组长度是13,每个元素都是一个完整的Unicode字符,也就是你看到的结果:

// arr.length = 13
[
  "",  "奈",  "樂", "",  "",  "",
  "",  "",  "",  "",  "❤",  "️",
  "藍"
]

为什么str.match(/[\s\S]/gu)和Array.from结果一致?

因为正则的u修饰符开启了Unicode模式,这时候正则会按Unicode码点来匹配字符,而不是按UTF-16代码单元,所以匹配的结果自然和Array.from的迭代结果完全相同。

总结对比

方法拆分逻辑数组长度对应值
str.split('')按单个UTF-16代码单元拆分str.length
Array.from(str)按Unicode码点(完整字符)遍历实际的Unicode字符数
str.match(/[\s\S]/gu)按Unicode码点匹配字符实际的Unicode字符数

如果你想让split也得到和Array.from一样的结果,可以用str.split(/(?=[\s\S])/u),不过一般直接用Array.from或者字符串迭代器会更直观。

备注:内容来源于stack exchange,提问作者Mr. X

火山引擎 最新活动