我现在想切换到Coffeescript一段时间,昨天我觉得我终于被卖了但是我在Coffeescript的阴影中偶然发现了Armin Ronachers article。
Coffeescript确实现在放弃了阴影,如果你对嵌套循环使用相同的迭代器就会出现这个问题。
var arr, hab, i;
arr = [[1, 2], [1, 2, 3], [1, 2, 3]];
for(var i = 0; i < arr.length; i++){
var subArr = arr[i];
(function(){
for(var i = 0; i < subArr.length; i++){
console.log(subArr[i]);
}
})();
}
因为cs只能在coffeescript
中声明变量阴影已被故意删除,我想了解为什么cs-authors想要摆脱这样的功能?
更新:以下是better example为什么影响很重要,源于有关此问题的问题on github
PS:我不是在找答案,告诉我我可以用反引号插入普通的Javascript。
答案 0 :(得分:25)
如果您阅读this ticket上的讨论,您可以看到CoffeeScript的创建者Jeremy Ashkenas解释禁止显式阴影之间的一些推理:
我们都知道,与词法范围相比,动态范围很差,因为它很难推断变量的值。对于动态范围,您无法通过读取周围的源代码来确定变量的值,因为该值完全取决于调用函数时的环境。如果允许并鼓励变量阴影,则无法确定变量的值而不在源中向后追踪到最接近的var变量,因为局部变量的完全相同的标识符在相邻的范围中可以具有完全不同的值。在所有情况下,当您想要隐藏变量时,只需选择一个更合适的名称即可完成相同的操作。如果局部变量名在整个词法范围内具有单个值,并且禁止阴影,则更容易推理您的代码。
因此,CoffeeScript非常谨慎地选择一石二鸟 - 通过删除“var”概念来简化语言,并禁止阴影变量作为自然结果。
如果您在CoffeeScript问题中搜索“范围”或“阴影”,您可以看到它始终出现。我不会在这里提出意见,但要点是,CoffeeScript Creators认为它会导致更简单的代码,而不易出错。
好的,我会稍微观察一下:阴影并不重要。你可以拿出人为的例子来说明为什么这两种方法都更好。事实是,无论是否有阴影,您都需要搜索范围链以“了解”变量的生命周期。如果您明确地将变量声明为JavaScript,则可能会更快地进行短路。但没关系。如果您不确定给定函数中的变量范围是什么,那么您做错了。
在CoffeeScript中可以隐藏,但不包括JavaScript。如果您实际需要一个您知道的本地范围的变量,您可以得到它:
x = 15
do (x = 10) ->
console.log x
console.log x
因此,在实际出现这种情况的可能性很小,有一个相当简单的解决方法。
就个人而言,我更喜欢显式声明 - 每变量的方法,并将提供以下作为我的“论据”:
doSomething = ->
...
someCallback = ->
...
whatever = ->
...
x = 10
...
这很有效。然后突然一个实习生出现并加上这一行:
x = 20
doSomething = ->
...
someCallback = ->
...
whatever = ->
...
x = 10
...
而且,bam,代码被破坏了,但破损直到后来才出现。哎呦!使用var
,这不会发生。但是,除非你另有说明,否则“通常是隐含的范围”,它会有。所以。反正。
我在一家在客户端和服务器上使用CoffeeScript的公司工作,我在实践中从未听说过这种情况。我认为无需在任何地方输入单词var
而节省的时间大于确定错误(从未出现过)的时间。
自写这个答案以来,我已经看到这个错误在实际代码中发生了两次。每次发生时,它都非常烦人且难以调试。我的感受已经发生了变化,认为CoffeeScript的选择在各地都很糟糕。
一些类似CoffeeScript的JS替代品,例如LiveScript和coco,使用两个不同的赋值运算符:=
来声明变量,:=
来修改外部作用域中的变量。这似乎是一个更复杂的解决方案,而不仅仅是保留var
关键字,而且一旦let
广泛使用,它也无法很好地保留。
答案 1 :(得分:7)
这里的主要问题不是阴影,它的CoffeeScript会混淆变量初始化和变量重新分配,而不允许程序员准确指定它们的意图
当咖啡脚本编译器看到x = 1
时,它不知道你是否意味着
我想要一个新变量,但我忘了我已经在较高的范围内使用该名称了
或
我想将值重新分配给我最初在文件顶部创建的变量
这不是你禁止在某种语言中使用阴影的方式。这就是你如何使用一种语言来惩罚那些意外地重复使用变量名称并且难以检测到错误的用户。
CoffeeScript可以设计为禁止阴影,但保留var
可以使声明和作业分开。编译器只会抱怨这段代码:
var x = blah()
var test = ->
var x = 0
“变量x已存在(第4行)”
但它也会抱怨这段代码:
x = blah()
test = ->
x = 0;
“变量x不存在(第1行)”
但是,由于删除了var
,编译器不知道你的意思是“声明”还是“重新分配”并且无法帮助。
对两个不同的东西使用相同的语法并不“简单”,即使它看起来像是这样。我推荐Rich Hickey's talk, Simple made easy,深入了解为何如此。
答案 2 :(得分:2)
因为一旦循环不能按预期工作,cs只声明变量。
这些循环的工作方式是什么?如果while i = 0 < arr.length
不为空,arr
中的条件将始终为真,因此它将是无限循环。即使它只有一个while
循环不能按预期工作(假设无限循环不是你想要的):
# This is an infinite loop; don't run it.
while i = 0 < arr.length
console.log arr[i]
i++
顺序迭代数组的正确方法是使用for ... in
构造:
arr = [[1,2], [1,2,3], [1,2,3]]
for hab in arr
# IDK what "hab" means.
for habElement in hab
console.log habElement
我知道这个答案听起来就像在切线上一样;这一点的主要原因是CS不鼓励变量阴影。但是,如果要用例子来支持或反对某些事情,那么这些例子应该是好的。这个例子无助于鼓励变量阴影的想法。
关于变量阴影问题,值得澄清的一件事是讨论是否应该在不同的函数范围之间允许变量阴影,而不是块。在相同的功能范围内,变量将提升整个范围,无论它们首先被分配到何处;这个语义继承自JS:
->
console.log a # No ReferenceError is thrown, as "a" exists in this scope.
a = 5
->
if someCondition()
a = something()
console.log a # "a" will refer to the same variable as above, as the if
# statement does not introduce a new scope.
有时会问的问题是为什么不添加一种方法来显式声明变量的范围,比如let
关键字(从而在封闭范围内隐藏其他具有相同名称的变量),或者生成{{ 1}}总是在该范围内引入一个新变量,并且有=
之类的东西来从封闭范围中分配变量而不在当前范围中声明一个变量。这样做的动机是避免这种错误:
:=
CoffeeScript关于没有阴影变量的特殊语法的论点是你根本不应该做这种事情。清楚地命名变量。因为即使允许使用阴影,使两个具有相同名称的两个不同含义的变量会非常混乱,一个在内部范围内,一个在一个封闭范围内。
根据您拥有的上下文使用足够的变量名称:如果您的上下文很少,例如一个顶级变量,你可能需要一个非常具体的名称来描述它,比如user = ... # The current user of the application; very important!
# ...
# Many lines after that...
# ...
notifyUsers = (users) ->
for user in users # HO NO! The current user gets overridden by this loop that has nothing to do with it!
user.notify something
(尤其是,如果它不是一个常数,它的值会随着时间而变化);如果你有更多的上下文,你可以使用较少的描述性名称(因为上下文已经存在),如循环变量:currentGameState
。
如果您想了解有关此设计决策的更多信息,您可能有兴趣阅读Jeremy Ashkenas在这些HackerNews主题中的观点:link,link;或讨论本主题的许多CoffeeScript问题:#1121,#2697和其他人。