为什么e + = 1和e = e + 1在CoffeeScript中的编译方式不同?

时间:2012-11-06 19:32:35

标签: javascript coffeescript global-variables compiler-bug

我一直认为<var> += 1<var> = <var> + 1在JS中具有相同的语义 现在,这个CoffeeScript代码在应用于全局变量e时编译为不同的JavaScript:

a: ->
  e = e + 1
b: ->
  e += 1

请注意b使用全局变量,而a定义局部变量:

({
  a: function() {
    var e;
    return e = e + 1;
  },
  b: function() {
    return e += 1;
  }
});

Try it yourself.
这是一个错误还是有这样的原因?

3 个答案:

答案 0 :(得分:10)

我想我会称这是一个错误,或者至少是一个无证的边缘情况或歧义。我没有在文档中看到任何明确指定何时在CoffeeScript中创建新局部变量的内容,因此它归结为通常的

  

当当前实施 X 时,我们会 X ,因为当前的实施就是这样做的。

有点像。

似乎触发创建新变量的条件是赋值:当你试图给它一个值时,看起来CoffeeScript决定创建一个新变量。所以这个:

a = ->
  e = e + 1

变为

var a;
a = function() {
  var e;
  return e = e + 1;
};

使用本地e变量,因为您明确指定了e一个值。如果您只是在表达式中引用e

b = ->
  e += 1

然后CoffeeScript不会创建新变量,因为它无法识别那里有e的赋值。 CS会识别一个表达式,但不够聪明,不能将e +=1视为等同于e = e + 1

有趣的是,当你使用属于CoffeeScript而不是JavaScript的op=表单时,CS确实会发现问题。例如:

c = ->
  e ||= 11

产生错误:

  

变量“e”不能用|| =赋值,因为它尚未定义

我认为对e += 1提出类似的抱怨是合情合理的。或者,所有a op= b表达式都应扩展为a = a op b并予以平等对待。


如果我们查看CoffeeScript源代码,我们可以看到正在发生的事情。如果你蠢蠢欲动,你会发现所有op=结构都会通过Assign#compileNode

compileNode: (o) ->
  if isValue = @variable instanceof Value
    return @compilePatternMatch o if @variable.isArray() or @variable.isObject()
    return @compileSplice       o if @variable.isSplice()
    return @compileConditional  o if @context in ['||=', '&&=', '?=']
  #...

因此对CoffeeScript特定的op=条件结构进行了特殊处理。快速审核表明a op= b非条件opop除了||&&?之外的直接传递通过JavaScript。那么compileCondtional发生了什么?好吧,正如所料,it checks that you're not using undeclared variables

compileConditional: (o) ->
  [left, right] = @variable.cacheReference o
  # Disallow conditional assignment of undefined variables.
  if not left.properties.length and left.base instanceof Literal and 
         left.base.value != "this" and not o.scope.check left.base.value
    throw new Error "the variable \"#{left.base.value}\" can't be assigned with #{@context} because it has not been defined."
  #...

我们从-> a ||= 11看到的错误消息以及评论时指出,如果a ||= b未在某处定义,则不允许您a

答案 1 :(得分:3)

这可以从文档中拼凑出来:

  • =Lexical scope

    中的作业
      

    CoffeeScript编译器会确保在词法范围内正确声明所有变量 - 您永远不需要自己编写var

         另一方面,函数中的

    inner不应该能够更改同名外部变量的值,因此具有自己的声明

    本节中给出的示例与您的情况完全相同。

  • +=||=

    这不是声明,因此上述内容不适用。在缺席的情况下,+=具有其通常含义,||=也是如此。

    事实上,由于这些不是由CoffeeScript重新定义的,因此它们从ECMA-262(基础目标语言)中获取其含义,​​从而产生您观察到的结果。

    不幸的是,这种“堕落”似乎没有明确记录。

答案 2 :(得分:2)

此问题最近才出现been discussed on CoffeeScript's Github Issues。似乎编译器的当前行为已在this previous issue上达成一致或至少已经讨论过。

基本上,在JavaScript中,表达式e = e + 1e += 1始终是等效的,因为它们从不引入新变量:它们总是将1添加到(本地或全局){{ 1}}变量,如果e它们将失败。现在,表达式typeof e === 'undefined'在JavaScript中有效,并且会声明var e = e + 1变量并将其分配给添加eundefined1的值,显然= P),而NaN在语法上无效。

在CoffeeScript中,如果之前未声明var e += 1,则e = e + 1可以是变量声明,如果在当前范围中定义了e,则e只是一个赋值语句,而{{1}永远不会引入一个新变量(一个有点合理的行为,因为增加一个先前未声明的变量没有意义)。

这是我理解的当前行为。我认为e += 1e = e + 1可能意味着不同的事情是不幸的,但我明白这是隐式变量声明和JavaScript的范围规则(这个this comment的组合)的结果。 ,可能很有偏见,解释)。