哪些字符与Array.from分组?


38

我一直在玩JS,无法弄清楚JS在使用时如何决定将哪些元素添加到创建的数组中Array.from()。例如,以下表情符号👍的a length为2,因为它由两个代码点组成,但是Array.from()将这两个代码点视为1,从而给出了一个元素数组:

const emoji = '👍';
console.log(Array.from(emoji)); // Output: ["👍"]

但是,某些其他字符也有两个代码点,例如此字符षि(也具有.length2个字符)。但是,Array.from不要将此字符“分组”,而是产生两个元素:

const str = 'षि';
console.log(Array.from(str)); // Output: ["ष", "ि"]

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


5
看看UTF-16代理对...
乔纳斯·威尔姆斯


1
我担心MDN的Array.from的polyfill具有不同的行为:-s
Ele

1
@Ele仅考虑带有的对象length。迭代器甚至Set不兼容
adiga

Answers:


26

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开始的连续代码单元的String值,从位于索引位置的代码单元开始。
  3. 将O。[[StringNextIndex]]设置为+ cp。[[CodeUnitCount]]。
  4. 返回CreateIterResultObject(resultString,false)。

CodeUnitCount是你感兴趣的是这个数字从何而来。提供codePointAt

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

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

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

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

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

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

    一个。返回记录{ [[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

但是👍的字符是:

console.log('👍'.charCodeAt()); // 55357, or 0xD83D
console.log('👍'.charCodeAt(1)); // 56397, or 0xDC4D

的第一个字符代码为'👍'D83D(十六进制),位于0xD800 to 0xDBFF前导替代字符的范围内。相反,的第一个字符代码'षि'要低得多,而没有。因此,'षि'将其分开,但'👍'没有。

षि是由两个独立的字符:梵文字母SSA,并且ि梵文元音登录我。当按此顺序彼此相邻时,尽管它们由两个单独的字符组成,但它们在视觉上以图形方式组合为单个字符。

相反,字符代码👍 只有在一起作为单个字形时才有意义。如果您尝试将一个代码点与另一个代码点一起使用而没有另一个,则将得到一个无意义的符号:

console.log('👍'[0]);
console.log('👍'[1]);


10
我认为,尽管大多数答案都是正确的,有用的,并且带有精心提供的引用,但该答案无法清楚地解释这两种情况之间的主要区别:从Unicode的角度来看,षि实际上是两个具有不同代码点的字符组合在一起形成一个字符字形(人类理解的一种抽象字符)。这与👍表情符号相反,表情符号本身就是一个完整的字符,即使其代码点足够高,因此也必须将其分成一个代理对。我相信澄清这可能有助于(否则很有价值)答案。
犀牛

具体来说,辅音ष(ṣ)和元音ि(i)以图形方式组合成音节षि(ṣi)
Amadan

@CertainPerformance“👍”中只有一个代码点。这表明此答案中的术语可能不正确。
本·阿斯顿


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)));


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)));


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

Javascript:将Unicode字符串转换为十六进制

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.