为啥扩展语法会将我的字符串转换为数组?

Posted

技术标签:

【中文标题】为啥扩展语法会将我的字符串转换为数组?【英文标题】:Why does spread syntax convert my string into an array?为什么扩展语法会将我的字符串转换为数组? 【发布时间】:2017-12-07 14:01:40 【问题描述】:

为什么展开语法会将我的字符串转换为数组?

var v = 'hello';
var [, ...w] = v; // ["e", "l", "l", "o"]

为什么w 不是字符串?

【问题讨论】:

我猜这和JS中的字符串类似数组有关吧? 在这种情况下,“标点符号”一词仅指...不是标识符或文字这一事实,它还描述了同一类别中的其他标记,例如=;因此,当您不编写 javascript 解析器时,它并不是一个特别具有描述性或有用的术语。所以“标点符号”是一个没有语义意义的低级术语。正如我们在x + y 中不将+ 称为标点符号一样,我们将其称为运算符,我们不会在... x 中将... 称为标点符号,我们将其称为扩展语法。该标准仅命名需要在其他地方引用的内容。 你能解释一下为什么你期望w 是一个字符串吗? 按照逻辑,第一个迭代器对象 Object value: 1, done: false 应该被丢弃,w 必须引用在第一个 yield 表达式之后暂停的生成器函数。 @ftor 我猜他们可以使用剩余的迭代器初始化其余变量,但是如果您想多次使用它,则必须将其显式转换为集合数据结构.可迭代协议不包含任何Type.fromIterable 逻辑。它被转换为数组是因为它使用数组语法,而且索引集合确实是最小公分母。 【参考方案1】:

Spread syntax(实际上是RobG 指出的标点符号)允许迭代散布到更小的位。由于字符串是可迭代的(它们在内部是字符数组,更具体地说是表示字符的有序整数序列),它们可以散布成单个字符。

接下来,对数组执行destructuring assignment 以解包和分组展开值。由于您用, 省略了字符数组的第一个元素并且没有分配引用,因此它丢失了,并且可迭代对象的 rest 被保存到w 中,分散到它的个体中部分,字符数组的单个字符。


此操作的具体语义由 ArrayAssignmentPattern 在ECMAScript 2015 Specification 中定义:[ Elisionopt AssignmentRestElement ] 产生式:

12.14.5.2 运行时语义:DestructuringAssignmentEvaluation

带参数

[...]

ArrayAssignmentPattern : [省略opt AssignmentRestElement ]

    iterator 为 GetIterator(value)。 ReturnIfAbrupt(迭代器)。 让 iteratorRecord 为 Record [[iterator]]: iterator, [[done]]: false。 如果存在省略,则 一种。令 status 为以 iteratorRecord 作为参数执行 Elision 的IteratorDestructuringAssignmentEvaluation 的结果。 湾。如果 status 是 abrupt completion,那么 一世。如果 iteratorRecord.[[done]] 为 false,则返回 IteratorClose(iterator, status)。 ii.返回Completion(状态)。 令 result 为以 iteratorRecord 作为参数执行 AssignmentRestElement 的IteratorDestructuringAssignmentEvaluation 的结果。 如果 iteratorRecord.[[done]] 为 false,则返回 IteratorClose(iterator, result )。 返回结果

这里,Elision是指一个或多个逗号(,)展开时的省略元素,顾名思义,相当于省略的音节,AssignmentRestElement指到将接收传播和解构值的目标,在这种情况下为w

这样做是首先从内部@@iterator 方法中获取对象的迭代器,然后逐步执行该迭代器,跳过@ 中的省略 产生式所指示的省略宽度的许多元素987654333@。完成后,它将逐步遍历 AssignmentRestElement 产生式的迭代器,并分配一个包含所有展开值的新数组——这就是w 的含义。它接收展开的单字符数组,解包以排除第一个字符。

从中获取迭代的@@iterator 方法是well-known Symbol,为一个对象更改它可以改变它的迭代方式,如Emissary's answer。具体来说,@@iterator method for String的默认实现如下:

21.1.3.27 String.prototype [@@iterator]()

当@@iterator 方法被调用时,它会返回一个迭代器对象 (25.1.1.2),该对象迭代一个字符串值的代码点,将每个代码点作为一个字符串值返回。

因此,迭代器允许通过单个代码点或字符串中的字符进行迭代——因此扩展字符串将产生其字符的数组。

【讨论】:

展开操作符是否返回除单个元素或数组以外的任何内容? @BenAston 它没有。它总是将一个可迭代对象分成最小的部分,然后相应地展开它们。 @BenAston 实际上,它甚至不应该返回数组以外的任何内容。 IIRC 永远不会返回单个元素。 是的,我的错。谢谢。 我的问题中的示例可能更常被称为“其余语法”(不传播-我的错误)。这个词法位置的其余语法捕获了可迭代的其余部分,并且似乎执行了从源可迭代(例如,本例中的字符串)到Array 的类型转换(如果需要)。我猜这是因为创建源类型的目标可迭代对象实际上并不总是可能的(因为可迭代对象是 AFAIK 只是具有Symbol.Iterator 属性的对象),因此使用了共同的分母Array。跨度> 【参考方案2】:

在 ES2015 中,扩展语法专门针对内部 String @@iterator 属性起作用 - 任何对象都可以通过将您自己的 iterator 或 generator / function* 分配给 obj[Symbol.iterator] 属性来以这种方式迭代。

例如,您可以更改新数组的默认行为...

const a = [...'hello'];
a[Symbol.iterator] = function* ()
    for(let i=0; i<this.length; ++i)
        yield `$this[i]!`;
;
console.log([...a]);

也可以更改字符串迭代器,但您必须显式创建一个String 对象。

【讨论】:

我认为字符串的可迭代性在 ES2015 中是新的? 最新规范中引入了价差运算符。但是您可以像以前的数组一样遍历字符串,即s = 'foo'; s[0] === 'f'; 那不是迭代。那是通过索引 AFAIK 检索。 重点是展开运算符,...thing 没有“扩展运算符”。 ... 是一个 punctuator,用于扩展语法 (SpreadElement) 和其余参数 (FunctionRestParameter)。【参考方案3】:

Spread syntax 只能应用于可迭代对象。由于 String 是可迭代的,Spread 运算符可以正常工作并将您的 char array(String) 拆分为 char's。

您可以使用以下示例进行检查,该示例证明 String 默认情况下是可迭代的。

var s = 'test';    
for (k in s) 
  console.log(k);

ECMAScript6 规范甚至提到了这个特定的字符串案例。

扩展运算符

将可迭代集合的元素(如数组or even a string)传播到文字元素和单个函数参数中。

http://es6-features.org/#SpreadOperator

var str = "foo";
var chars = [ ...str ]; // [ "f", "o", "o" ]

值得一提的是,这是一种特殊情况,仅当您使用带有扩展运算符的直接字符串时才会发生。当你在数组中给出单个字符串时,整个数组将被视为可迭代对象,而不是里面的字符串。

var str = [ "hello" ,2 ];
var other = [  ...str ]; // [  "hello" ,2 ]

我知道上面的例子没有多大意义,只是为了传达这样一个事实,即在这种情况下 String 将被区别对待。

【讨论】:

不是我的反对票,但没有“扩展运算符”,根据 OP,它是“扩展语法”(已编辑)。我猜原来的 MDN 文章也弄错了,所以文本已经更新,但 URL 没有更新。 @RobG 好吧,OP 最初确实有“操作员”,但我对其进行了编辑。从技术上讲,操作员不应该只评估一个单一的结果吗?由于展开语法不只是计算一个项目,它不会是一个运算符。 Axel Rauschmayer 识别扩展和休息运算符。 exploringjs.com/es6/ch_destructuring.html#sec_rest-operator。该规范仅提及SpreadElement AFAICT。 @RobG 我会选择 ECMA-262。老实说,我更喜欢语法而不是运算符(只是因为我觉得它更正确)但如果规范说标点符号我会去。 @BenAston 只是一点点更新:我做了一些研究,发现它客观地不是运算符而是语法:see my q&a here。【参考方案4】:

这里发生了两件事。

首先,您使用的是数组解构。这允许您获取一个可迭代对象并将其值分配给各个变量。

其次,您正在使用休息参数。这会将可迭代的任何剩余输出转换为数组。

所以你的字符串'hello'被迭代成它的单个字符,第一个被忽略(因为你省略了目标),然后剩下的字符被转换成一个数组并分配给w

【讨论】:

【参考方案5】:

因为 javascript 中的字符串在某种程度上被视为字符数组。

例如,当你对一个字符串(比如hello)执行 for each 循环时,使用 lodash:

_.forEach('hello', function(i) 
    console.log(i)
)

它输出:

h
e
l
l
o

slice() 等函数也适用于字符串和数组。

【讨论】:

为什么在这个例子中需要 lodash? 因为我不知道如何做一个常规的 javascript forEach 循环了。 JavaScript 中的字符串不是字符数组。 字符串不是“字符数组”。数组是一种特殊的对象,字符串是提供方便属性(如长度)的原语,其字符可以通过索引访问。所以它们符合"iterable" 的要求,但数组与参数、NodeList 和集合一样多(它们都不是数组)。 啊,感谢您对此的澄清。我一直认为它是,因为它可以迭代,具有length 属性,它们都可以使用slice() 等。

以上是关于为啥扩展语法会将我的字符串转换为数组?的主要内容,如果未能解决你的问题,请参考以下文章

这里使用的扩展语法如何将数字转换为字符串数组?

将结构数组转换为字符串数组以显示为表格

Laravel 数组到字符串的转换

将字符串转换为numpy数组

将rowdatapacket转换为数组,如何将mysql node.js api rowdatapacket转换为数组,将字符串转换为数组

将我的 FBX 文件转换为 .gltf 后,模型非常小,为啥?