7

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

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

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

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

自己试试。
这是一个错误还是有原因?

4

3 回答 3

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.

有趣的是,当您使用op=属于 CoffeeScript 而不是 JavaScript 的表单时,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 ['||=', '&&=', '?=']
  #...

因此,op=正如预期的那样,对特定于 CoffeeScript 的条件结构进行了特殊处理。快速回顾表明,a op= b对于非条件op(即,和op之外的 s )直接传递给 JavaScript。那么这是怎么回事?好吧,正如预期的那样,它会检查您是否没有使用未声明的变量||&&?compileCondtional

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

于 2012-11-06T20:09:50.897 回答
3

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

  • =:词法范围内的赋值

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

    inner另一方面,在函数内部,不应更改同名外部变量的值,因此有自己的声明

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

  • +=||=

    这不是声明,因此上述内容不适用。在它不存在的情况下,+=它具有其通常的含义,就像||=.

    事实上,由于 CoffeeScript 没有重新定义这些,它们的含义来自 ECMA-262(底层目标语言),它产生了您观察到的结果。

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

于 2012-11-06T20:25:42.837 回答
2

最近在 CoffeeScript 的 Github 问题上讨论了这个问题似乎编译器的当前行为已在上一个问题上达成一致,或至少已讨论过。

基本上,在 JavaScript 中,表达式e = e + 1e += 1总是等价的,因为它们从不引入新变量:它们总是添加1到(局部或全局)e变量中,否则如果typeof e === 'undefined'. 现在,该表达式var e = e + 1在 JavaScript 中有效,并将声明e变量并将其分配给加undefined1( NaN,显然 =P) 的值,而var e += 1在语法上是无效的。

在 CoffeeScript中,如果之前没有声明,e = e + 1可以是一个变量声明,或者如果在当前范围内定义,则只是一个赋值语句,而从不引入新变量(这是一种有点合理的行为,因为增加 a 没有意义以前未声明的变量)。eee += 1

这是我理解的当前行为。我认为这有点不幸,e = e + 1并且e += 1可能意味着不同的东西,但我理解这是隐式变量声明和 JavaScript 范围规则相结合的结果(这个评论可能是相当有偏见的解释)。

于 2012-11-06T20:54:16.683 回答