是传播“语法”还是传播“运算符”?

IT技术 javascript ecmascript-6 language-lawyer spread-syntax
2021-01-23 12:31:33

我听说过...“扩展语法”和“扩展运算符都被称为,后者更受欢迎。相关MDN 文档的 URL表明它最初被称为扩展运算符,但后来更改为扩展语法,并且MDN 的运算符列表没有提及它。

谷歌似乎暗示运营商这个词更受欢迎和被接受,微软文档es6-features.org等网站就是这样引用它的。

在 ECMAScript 的上下文中,哪个术语最正确(如果有的话),为什么?数组解构赋值呢?

2个回答

它不是运营商。

在这个词的所有意义上,它都不是一个。自从它被引入以来一直是一个巨大的误解,尽管有流行的意见——它不是一个,并且有一些客观的观点需要提出:

  • 它不符合运算符的定义
  • 它不能用作运算符
  • 语言规范暗示它不是运算符

应该提到的是,扩展语法有不同的“风格”,在不同的上下文中使用,并且在使用相同标点符号时通常以不同的名称引用。传播语法基本上是...标点符号应用的总称,请参阅Felix Kling的精彩回答,详细说明了所有用法和名称。关于这些个人用途的更多解释在补充答案中给出

什么是运算符?

从语义上讲,在 ECMAScript 的上下文中,运算符只是内置函数,它们接受参数并计算出单个值——用前缀、中缀或后缀表示法编写,并且通常带有符号名称,例如+/来自维基百科

简单地说,以某种方式计算涉及运算符的表达式,结果值可能只是一个值(一个 r 值),或者可能是一个允许赋值的对象(一个左值)。

例如,+运算符产生一个值,例如 2,这是一个右侧表达式,而.运算符产生一个允许赋值的对象,例如foo.bar左侧表达式。

从表面上看,...标点符号1看起来是一个前缀一元运算符:

const baz = [foo, ...bar];

但是这个论点的问题在于...bar它的计算结果不是奇异值;bar一个一个地传播 iterable的元素。传播参数也是如此:

foo(...bar);

在这里,从 iterablefoo接收单独的参数bar它们是传递给 的单独值foo,而不仅仅是一个值。它不符合运算符的定义,所以它不是一个。

为什么不是运营商?

需要说明的另一点是运算符是独立的并返回单个值。例如:

const bar = [...foo];

如前所述,这很有效。当您尝试执行此操作时会出现问题:

const bar = ...foo;

如果 spread 语法是一个运算符,则后者可以正常工作,因为运算符将表达式计算为单个值,但 spread 不是,因此它会失败。传播语法和传播参数仅适用于数组和函数调用的上下文,因为这些结构接收由传播数组元素或参数提供的多个值。对多个值求值超出了操作员的能力范围。

标准怎么说?

运算符的完整列表在ECMAScript 2015 语言规范的第 12.5 节到第 12.15 节中列出,该规范...被引入,但没有提及.... 也可以推断出它不是运算符。此答案中提到的两种主要情况,其中扩展语法在生产中,用于函数调用(扩展参数)或数组文字(扩展语法)如下所述:

数组文字:
  [省略选择]
  [元素列表]
  [元素列表,Elisionopt]

元素列表:
  Elision opt赋值表达式
  Elision选择SpreadElement
  ElementList , Elision选择AssignmentExpression
  ElementList , Elision选择SpreadElement

省略:
  ,
  省略,

传播元素:
  ...赋值表达式
  

对于函数调用

调用表达式:
  MemberExpression 参数

参数:
  ( )
  ( 参数列表 )

参数列表:
  赋值表达式
  ...赋值表达式
  参数列表,赋值表达式
  ArgumentList , ... AssignmentExpression
  

在这些作品中,可以得出一个结论:传播“运算符”不存在。如前所述,运算符应该是独立的,就像 inconst bar = ...foo和 评估为一个单一的值。该语言的语法阻止了这种情况,这意味着传播语法从来都不是独立的。它是数组初始值设定项和函数调用的扩展,是它们语法的扩展。

为什么要传播“语法”?

维基百科定义的语法

在计算机科学中,计算机语言的语法是定义符号组合的一组规则,这些符号组合被认为是该语言中结构正确的文档或片段。

语法基本上是语言的“形式”,即关于代码外观和代码编写方式合法与否的规则。在这种情况下,ECMAScript 的语法专门定义了...标点符号只出现在函数调用和数组字面量中作为扩展——这是一个定义...foo被认为是合法的符号 ( )组合的规则,因此它的语法类似于箭头函数 ( =>)如何不是运算符,而是语法2

调用...接线员是用词不当。运算符是一个内置函数,它接受参数(操作数)并采用前缀、中缀或后缀符号的形式,并且计算结果恰好是一个值...,虽然满足前两个条件,但不满足最后一个。...相反,它是语法,因为它是在语言的语法中明确而明确地定义的。因此,“扩展运算符”在客观上更准确地称为“扩展语法”。


1术语“标点符号”是指ECMAScript 2015及更高版本规范中的标点符号这些符号包括语法组件和运算符,并且是语言的标点符号...是标点符号本身,但术语“扩展语法”是指标点符号的整个应用。

2 =>本身是一个标点符号,正如...我所指的是箭头函数语法=>标点符号 ( (…) => { … })的应用,就像扩展语法是指...标点符号的应用一样

@RobG 是的,我引用了第 6 版,因为它是当时引入的。我会确保编辑并提及所有操作员都已提及,或者如果您愿意,也可以。
2021-03-11 12:31:33
赞成这项研究。仍然想知道为什么这很重要?如果它已经通俗地称为“传播运算符”,我怀疑有人会误解对话中的含义。
2021-03-23 12:31:33
对我来说,...是用于扩展语法和其余参数的标点符号,它本身既不是运算符也不是语法。它等效于其他标点符号,例如,, ;并且:用于语法的命名部分(参数列表、语句、对象文字),但不称为“运算符”。此外,该规范的最新版本是ECMAScript 2017,我猜你引用了 2015 年,因为那...是引入的地方最后,第 12.5 节到第 12.6 节提到了所有运算符,但...不是其中之一。抱歉,只能给你一个赞——为你的努力投上一票。;-)
2021-03-30 12:31:33
@PatrickRoberts 假设这个人即将成为 JS 语法纳粹,但仍然不知道他/她是否应该惩罚说“传播运算符”的人。这个答案提供了一个很好的解释。
2021-04-02 12:31:33
@PatrickRoberts 你是对的,但我想区分一下。对我来说最大的问题是没有一个权威的帖子给出了这个问题的肯定答案,这个问题源于为什么 MDN 有语法而其他人都有操作符。我只是想分享我的知识,但也展示了不同之处。
2021-04-07 12:31:33

语法的其他用途

主要答案中未涵盖传播/休息语法的其他多种用途。他们包括:

  • 函数参数中的其余语法
  • 数组和对象1解构赋值
  • 对象字面量中的对象传播语法1

休息语法

扩展语法的使用,通常称为休息语法,用于函数参数中可变数量的参数这与传播参数不同,传播参数用于将参数传递基于可迭代元素的函数调用例如:

function add(...addends) {
  …
}

在这里,函数使用了 rest 语法add来接收标识符中其余参数addends这似乎addends与传递参数的数组一样计算为奇异值,但是如果我们尝试:

function foo(...[bar, baz]) {
  …
}

在这里,barbaz都将被分配一个与传递的第一个和第二个参数相对应的值——因此这并不总是评估为一个值。潜在的问题是,...addends在第一个示例和...[bar, baz]第二个示例中,实际上根本不计算值 - 它只是在将参数数组分配给标识符的操作期间使用。因此,它的语法允许函数的参数数量可变,而不是运算符。

解构赋值

Spread 语法也可以在数组解构赋值期间使用,实际上在语言规范中被称为剩余元素(因为在解构中使用时,它获得了解构后的可迭代对象的其余部分)。可以提出令人信服的论点,因为这看起来像一个运算符:

const [...bar] = [1, 2, 3];

它像前缀一元运算符一样使用。此处,bar计算为[1, 2, 3] — 单个值。但这并不总是发生,例如:

const [first, ...[second, third]] = [1, 2, 3];

此处,firstsecond和 分别third计算为 1、2 和 3。但是...[second, third]分配给两个标识符,而不是一个,并且不会评估为单一值,而是两个。就像 rest 语法一样,潜在的问题是,...bar在第一个示例和...[second, third]第二个示例中,实际上根本没有计算出一个值——它只是在赋值操作期间使用。因此,它根本不是运算符2,只是帮助解压缩值的新语法。

对象传播语法

扩展语法的最后一个用途是对象字面量,通常称为“对象扩展属性”,其中目标对象自己的可枚举属性被扩展到另一个对象,例如:

const foo = { ...bar };

这不是运算符,就像数组展开语法不是运算符一样。概念是相同的,而不是数组中的索引和元素,bar的可枚举键和值分布到foo这里,收集bar的属性是传播-不只是一个单一的值,因此它不适合运营商的定义。


1 Object rest/spread 属性目前在 ECMAScript 的第 3 阶段提案中,并且很可能在不久的将来添加

2将解构赋值作为运算符的另一个问题,除了语义之外,是语言规范将其定义为补充语法——而不是补充运算符,这是理所当然的。它不是独立的,因为这不起作用:

const ...bar = [1, 2, 3, 4];

它是上下文的,只允许在语言的语法、对象字面量和左侧表达式的数组字面量中。它也是优化左侧表达式解释的语法同样,这是为语言添加新语法的扩展,是对现有语法的改进。这重申了规范的论点。