哪些字符与 Array.from 分组?

Shn*_*ick 40 javascript string unicode iterator

我一直在玩 JS,但无法弄清楚 JS 如何决定在使用Array.from(). 例如,以下表情符号的 alength为 2,因为它由两个代码点组成,但是,Array.from()将这两个代码点视为一个,给出一个包含一个元素的数组:

const emoji = '';
console.log(Array.from(emoji)); // Output: [""]
Run Code Online (Sandbox Code Playgroud)

但是,其他一些字符也有两个代码点,例如这个字符??(也有一个.length2)。但是,Array.from不会“分组”此字符,而是生成两个元素:

const str = '??';
console.log(Array.from(str)); // Output: ["?", "?"]
Run Code Online (Sandbox Code Playgroud)

我的问题是:当字符由两个代码点组成时,是什么决定了字符是被分解(如示例二)还是被视为一个单一元素(如示例一)?

Cer*_*nce 27

Array.from首先尝试调用参数的迭代器(如果它有),而字符串确实有迭代器,所以它会调用String.prototype[Symbol.iterator],所以让我们看看原型方法是如何工作的。它在此处的规范中描述:

  1. 让 O 成为?RequireObjectCoercible(这个值)。
  2. 让 S 是?ToString(O)。
  3. 返回 CreateStringIterator(S)。

查找CreateStringIterator最终会将您带到21.1.5.2.1 %StringIteratorPrototype%.next ( ),它会:

  1. 让cp成为!CodePointAt(s, 位置)。
  2. 令 resultString 为包含 cp.[[CodeUnitCount]] 来自 s 的连续代码单元的字符串值,该代码单元从索引位置处的代码单元开始。
  3. 将 O.[[StringNextIndex]] 设置为位置 + cp.[[CodeUnitCount]]。
  4. 返回 CreateIterResultObject(resultString, false)。

这CodeUnitCount就是你感兴趣的。这个数字来自CodePointAt:

  1. 首先是字符串中索引位置的代码单元。
  2. 让 cp 是其数值为 first 的代码点。
  3. 如果 first 不是前导代理或尾随代理,则

    一种。返回记录{ [[CodePoint]]: cp, [[CodeUnitCount]]: 1, [[IsUnpairedSurrogate]]: false }。

  4. 如果 first 是尾随代理或位置 + 1 = 大小,则

    a { [[CodePoint]]: cp, [[CodeUnitCount]]: 1, [[IsUnpairedSurrogate]]: true }.返回记录。

  5. 让 second 成为字符串中索引位置 + 1 处的代码单元。

  6. 如果 second 不是尾随代理,则

    一种。返回记录{ [[CodePoint]]: cp, [[CodeUnitCount]]: 1, [[IsUnpairedSurrogate]]: true }。

  7. 将 cp 设置为 ! UTF16DecodeSurrogatePair(第一,第二)。

  8. 返回记录{ [[CodePoint]]: cp, [[CodeUnitCount]]: 2, [[IsUnpairedSurrogate]]: false }。

因此,当使用 迭代字符串时Array.from,仅当所讨论的字符是代理对的开头时,它才返回 CodeUnitCount 为 2。这里描述了被解释为代理对的字符:

此类操作对具有0xD800 到 0xDBFF范围内数值的每个代码单元(由 Unicode 标准定义为前导代理,或更正式地定义为高代理代码单元)和每个具有数值的代码单元进行特殊处理在 0xDC00 到 0xDFFF(定义为尾随代理,或更正式地定义为低代理代码单元)的包含范围内,使用以下规则..:

?? 不是代理对:

console.log('??'.charCodeAt()); // First character code: 2359, or 0x937
console.log('??'.charCodeAt(1)); // Second character code: 2367, or 0x93F
Run Code Online (Sandbox Code Playgroud)

但是的字符是:

console.log(''.charCodeAt()); // 55357, or 0xD83D
console.log(''.charCodeAt(1)); // 56397, or 0xDC4D
Run Code Online (Sandbox Code Playgroud)

的第一个字符代码''是十六进制的 D83D,在0xD800 to 0xDBFF前导代理的范围内。相比之下,的第一个字符代码'??'要低得多,而事实并非如此。所以'??'会分开,但''不会。

??由两个独立的字符组成:?,梵文字母 Ssa和?,梵文元音符号 I。当按此顺序彼此相邻时,尽管它们由两个单独的字符组成,但它们在视觉上会以图形方式组合成一个字符。

相比之下, 只有当作为单个字形组合在一起时,字符代码才有意义。如果您尝试使用带有任一代码点而没有其他代码点的字符串,您将得到一个无意义的符号:

console.log(''[0]);
console.log(''[1]);
Run Code Online (Sandbox Code Playgroud)

  • 我认为,虽然大部分是正确的、有用的,并且有仔细提供的引用,但这个答案未能清楚地解释这两种情况之间的关键区别:从 Unicode 的角度来看,“षि”实际上是*两个*具有不同代码点的字符组合形成单个字形(一个*抽象*字符,如人类所理解的)。这与“表情符号”形成鲜明对比,“表情符号”本身就是一个完整的字符,尽管它的代码点足够高以至于必须将其拆分为代理对。我相信澄清这一点可以对这个(否则很有价值)的答案有很大帮助。 (10认同)

Jon*_*lms 13

UTF-16(js 中用于字符串的编码)使用 16 位单位。因此,可以使用 15 位表示的每个 unicode 都表示为一个代码点,其他所有内容都表示为两个,称为代理对。该字符串的迭代器遍历代码点。

维基百科上的 UTF-16


Gré*_*EUT 8

这都是关于字符背后的代码。有些以两个字节 (UTF-16) 编码,并被解释Array.from为两个字符。必须检查字符列表:

http://www.fileformat.info/info/charset/UTF-8/list.htm

http://www.fileformat.info/info/charset/UTF-16/list.htm

function displayHexUnicode(s) {
  console.log(s.split("").reduce((hex,c)=>hex+=c.charCodeAt(0).toString(16).padStart(4,"0"),""));
}

displayHexUnicode('??');

console.log(Array.from('??').forEach(x => displayHexUnicode(x)));
Run Code Online (Sandbox Code Playgroud)


function displayHexUnicode(s) {
  console.log(s.split("").reduce((hex,c)=>hex+=c.charCodeAt(0).toString(16).padStart(4,"0"),""));
}

displayHexUnicode('');

console.log(Array.from('').forEach(x => displayHexUnicode(x)));
Run Code Online (Sandbox Code Playgroud)


对于显示十六进制代码的函数:

Javascript:Unicode 字符串到十六进制