我一直认为<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.
这是一个错误还是有这样的原因?
答案 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
非条件op
(op
除了||
,&&
和?
之外的直接传递通过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)
这可以从文档中拼凑出来:
CoffeeScript编译器会确保在词法范围内正确声明所有变量 - 您永远不需要自己编写
另一方面,函数中的var
。
inner
不应该能够更改同名外部变量的值,因此具有自己的声明。
本节中给出的示例与您的情况完全相同。
+=
和||=
这不是声明,因此上述内容不适用。在缺席的情况下,+=
具有其通常含义,||=
也是如此。
事实上,由于这些不是由CoffeeScript重新定义的,因此它们从ECMA-262(基础目标语言)中获取其含义,从而产生您观察到的结果。
不幸的是,这种“堕落”似乎没有明确记录。
答案 2 :(得分:2)
此问题最近才出现been discussed on CoffeeScript's Github Issues。似乎编译器的当前行为已在this previous issue上达成一致或至少已经讨论过。
基本上,在JavaScript中,表达式e = e + 1
和e += 1
始终是等效的,因为它们从不引入新变量:它们总是将1
添加到(本地或全局){{ 1}}变量,如果e
它们将失败。现在,表达式typeof e === 'undefined'
在JavaScript中有效,并且会声明var e = e + 1
变量并将其分配给添加e
和undefined
(1
的值,显然= P),而NaN
在语法上无效。
在CoffeeScript中,如果之前未声明var e += 1
,则e = e + 1
可以是变量声明,如果在当前范围中定义了e
,则e
只是一个赋值语句,而{{1}永远不会引入一个新变量(一个有点合理的行为,因为增加一个先前未声明的变量没有意义)。
这是我理解的当前行为。我认为e += 1
和e = e + 1
可能意味着不同的事情是不幸的,但我明白这是隐式变量声明和JavaScript的范围规则(这个this comment的组合)的结果。 ,可能很有偏见,解释)。