为何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




