【问题标题】:Is it spread "syntax" or the spread "operator"?是传播“语法”还是传播“运算符”?
【发布时间】:2017-12-09 15:07:11
【问题描述】:

我听说... 被称为“传播语法”和“传播运算符”,后者更受欢迎。相关MDN documentation的URL表明它最初被称为传播运算符,但后来改为传播语法,MDN's list of operators没有提及它。

Google 似乎暗示 operator 一词更受欢迎和被接受,Microsoft documentationes6-features.org 等网站也这样称呼它。

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

【问题讨论】:

    标签: javascript ecmascript-6 language-lawyer spread-syntax


    【解决方案1】:

    它不是运算符。

    在这个词的所有意义上,它都不是一个。自推出以来,它一直是一个巨大的误解,尽管流行的观点 - 它不是一个,并且有一些客观的观点需要说明:

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

    应该提到,扩展语法有不同的“风格”,在不同的上下文中使用,并且在使用相同的标点符号时通常用不同的名称来引用。扩展语法基本上是... 标点符号应用的总称,请参阅Felix Kling 的出色答案,详细说明所有用途和名称。有关这些个人用途的更多说明,请参见the supplementary answer

    什么是运算符?

    在语义上,在 ECMAScript 的上下文中,运算符只是内置函数,它们接受参数并评估为 单个值——以前缀、中缀或后缀表示法编写,通常使用符号名称,例如如+/。来自Wikipedia

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

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

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

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

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

    foo(...bar);
    

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

    为什么不是运算符?

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

    const bar = [...foo];
    

    如前所述,这很好用。当您尝试这样做时会出现问题:

    const bar = ...foo;
    

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

    标准是怎么说的?

    完整的运算符列表在 ECMAScript 2015 Language Specification 的第 12.5 至 12.15 条中列出,该规范引入了 ...,但未提及 ...。也可以推断它不是算子。此答案中提到的两种主要情况,其中扩展语法在生产中,用于函数调用(扩展参数)或array literals(扩展语法)如下所述:

    ArrayLiteral :
      [省略opt]
      [元素列表]
      [ 元素列表 , Elisionopt ]
    
    元素列表:
      省略opt 赋值表达式
      省略opt SpreadElement
      ElementList , Elisionopt 赋值表达式
      ElementList , Elisionopt SpreadElement
    
    省略:
      ,
      省略,
    
    传播元素:
      ... 赋值表达式
      

    对于function calls

    调用表达式:
      成员表达式参数
    
    论据:
      ( )
      (参数列表)
    
    参数列表:
      赋值表达式
      ... 赋值表达式
      参数列表,赋值表达式
      ArgumentList , ... 赋值表达式
      

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

    为什么要传播“语法”?

    语法,由Wikipedia定义:

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

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

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


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

    2=> 本身是一个标点符号,就像... 一样,但我具体指的是箭头函数语法=> 标点符号 ((…) => { … }) 的应用,正如 spread syntax 指的是 ... 标点符号的应用。

    【讨论】:

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

    语法的其他用途

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

    • 函数参数中的 Rest 语法
    • 数组和对象1解构赋值
    • 对象字面量中的对象扩展语法1

    休息语法

    扩展语法的用法,通常称为rest 语法,用于函数的参数 中可变数量的参数。这与散布参数不同,散布参数用于将参数传递给基于可迭代元素的函数调用。例如:

    function add(...addends) {
      …
    }
    

    这里,rest 语法用于函数add 接收标识符addends 中参数的rest。这似乎确实评估为奇异值,因为 addends 是传递参数的数组,但如果我们尝试一下:

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

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

    解构赋值

    spread 语法也可以在array destructuring assignment 期间使用,实际上在语言规范中被称为休息元素(因为在解构中使用时,它会获得解构后的迭代的其余部分)。可以提出一个令人信服的论点,因为这看起来确实像一个运算符:

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

    它被用作前缀一元运算符。这里,bar 的计算结果为 [1, 2, 3] — ,这是一个单一值。但这并不总是发生,例如:

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

    在这里,firstsecondthird 分别计算为 1、2 和 3。但是...[second, third] 分配给两个标识符,而不是一个,并且不会评估为一个奇异值,而是两个。就像 rest 语法一样,根本问题是第一个示例中的 ...bar 和第二个示例中的 ...[second, third] 实际上根本没有计算出一个值——它只是在任务。因此,它根本不是一个运算符2,只是帮助解包值的新语法。

    对象传播语法

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

    const foo = { ...bar };
    

    这不是运算符,就像数组扩展语法不是运算符一样。概念是一样的,而不是数组中的索引和元素,bar 的可枚举键和值传播到foo。在这里,bar 的属性的集合是散布的——不仅仅是一个单一的值,因此它不符合运算符的定义。


    1Object rest/spread properties 目前处于 ECMAScript 的第 3 阶段提案中,很可能会在不久的将来添加

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

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

    它是上下文相关的,只有语言的语法、对象字面量和左侧表达式的数组字面量才允许存在。它也是改进左侧表达式解释的语法。同样,这是向语言添加新 语法 的扩展,是对现有语法的改进。这再次证实了规范的论点。

    【讨论】:

      猜你喜欢
      • 2017-06-30
      • 2019-04-25
      • 2020-07-12
      • 2016-10-16
      • 1970-01-01
      • 2015-02-26
      • 2020-08-08
      • 2014-07-31
      • 2018-08-12
      相关资源
      最近更新 更多