Array.from
首先会尝试调用参数的迭代器(如果有的话),而字符串确实有迭代器,所以它会调用
String.prototype[Symbol.iterator]
,那么我们来看一下原型方法是如何工作的。在规范
这里中有描述。
查找
CreateStringIterator
最终会导致你到达
21.1.5.2.1 %StringIteratorPrototype%.next ( )
,其中执行以下操作:
第 9 步: 获取当前代码点
第 10 步: 创建一个包含从位置索引开始和由 cp.[[CodeUnitCount]] 连续的代码单元组成的字符串值 resultString。
第 11 步: 将 O.[[StringNextIndex]] 设置为 position + cp.[[CodeUnitCount]]。
第 12 步: 返回结果对象 CreateIterResultObject(resultString, false)。
你需要关注的是
CodeUnitCount
。这个数字来自于
CodePointAt:
第 3 步: 让 first 是字符串中位置索引处的代码单元。
第 4 步: 让 cp 是其数值等于 first 的代码点。
如果 first 不是前导代理项或尾随代理项,则返回记录
{ [[CodePoint]]: cp, [[CodeUnitCount]]: 1, [[IsUnpairedSurrogate]]: false }
。
如果 first 是尾随代理项或 position + 1 = size,则返回记录
{ [[CodePoint]]: cp, [[CodeUnitCount]]: 1, [[IsUnpairedSurrogate]]: true }
。
让 second 是字符串中位置索引为 position + 1 的代码单元。
如果 second 不是尾随代理项,则返回记录
{ [[CodePoint]]: cp, [[CodeUnitCount]]: 1, [[IsUnpairedSurrogate]]: true }
。
将 cp 设置为 ! UTF16DecodeSurrogatePair(first, second)。
返回记录
{ [[CodePoint]]: cp, [[CodeUnitCount]]: 2, [[IsUnpairedSurrogate]]: false }
。
因此,使用
Array.from
迭代字符串时,仅当所讨论的字符是代理对的开头时,它才返回 CodeUnitCount 为 2。被解释为代理对的字符在
这里 中描述:
此类操作对于数值在包括范围 0xD800 到 0xDBFF 内的每个代码单元(由 Unicode 标准定义为前导代理或更正式地称为高代理代码单元)以及数值在包括范围 0xDC00 到 0xDFFF 内的每个代码单元(定义为尾随代理或更正式地称为低代理代码单元),应用以下规则..:
षि
不是代理对:
console.log('षि'.charCodeAt());
console.log('षि'.charCodeAt(1));
但是
的字符是:
console.log(''.charCodeAt());
console.log(''.charCodeAt(1));
''
的第一个字符代码是D83D,以十六进制表示,它在前导代理范围内的0xD800到0xDBFF之间。相比之下,'षि'
的第一个字符代码要低得多,并且不在该范围内。因此,'षि'
被分开,但''
没有。
षि
由两个单独的字符组成:ष
,天城体字母Ssa和ि
,天城体元音符号I。当按照这个顺序挨在一起时,它们在视觉上合并为一个单独的字符,尽管它们由两个单独的字符组成。
相比之下,
的字符代码仅在作为单个字形在一起时才有意义。如果你试图使用其中一个代码点的字符串而没有另一个,你将得到一个无意义的符号:
console.log(''[0]);
console.log(''[1]);
Array.from()
方法将字符串转换为一个unicode数组,然后在此数组上使用.length
属性即可获得字符串的正确长度。 - adiga