CoffeeScript中的函数声明

80

我注意到在CoffeeScript中,如果我使用以下方式定义一个函数:

a = (c) -> c=1

我只能获得函数表达式:

var a;
a = function(c) {
    return c = 1;
};

但是,个人经常使用函数声明,例如:

function a(c) {
    return c = 1;
}

我确实使用第一种形式,但我想知道在CoffeeScript中是否有一种生成函数声明的方法。如果没有这样的方法,我想知道为什么CoffeeScript要避免这样做。我认为只要函数在作用域的顶部被声明,JSLint不会报错。


4
你是否有任何想要函数声明的好理由?如果你正在使用CoffeeScript,除非编译后的JS格式不正确或存在缺陷,否则你不需要关心它的格式。请注意,本翻译已尽可能保持原意,同时使语言更通俗易懂,但并未添加解释或其他额外内容。 - Raynos
3
在大多数情况下,函数声明和函数表达式的用法是相同的,但它们之间存在一些细微的差别。例如:https://developer.mozilla.org/en/JavaScript/Reference/Functions_and_function_scope#Conditionally_defining_a_function 因此,在某些情况下,它们并不相等。 - Grace Huang
你给我提供了一段代码,其中函数声明是未定义行为。你想使用函数声明而不是函数表达式,以便可以“滥用”未定义的行为吗? - Raynos
5
函数声明可以方便调试,比如可以将函数名与函数联系起来以生成堆栈跟踪。这也是 CoffeeScript 在 class 中使用函数声明的原因。 - Trevor Burnham
2
@TrevorBurnham 我的意思是这只是在调试编译后的JS时的微小改进。你实际上想要的是一个可以读取CoffeeScript的调试器。 - Raynos
显示剩余4条评论
7个回答

61

CoffeeScript只在一个地方使用函数声明(又称“命名函数”):类定义。例如,

class Foo

编译成

var Foo;
Foo = (function() {
  function Foo() {}
  return Foo;
})();
根据FAQ,CoffeeScript之所以不在其他地方使用函数声明的原因是:

这要怪微软。最初,可以为每个有合适名称的函数分配一个名称,但是IE 8及更早版本存在作用域问题,其中命名函数被视为声明和表达式两种情况。有关更多信息,请参见这里

简而言之:不谨慎使用函数声明可能导致IE(9之前版本)和其他JS环境之间的不一致性,因此CoffeeScript避免使用它们。

31
他在谈论IE浏览器对于具名函数表达式(例如 var a = function a() {};)的问题。函数声明(例如 function a() {})没有跨浏览器的这些不一致性。 - AngusC
4
如果在浏览器中使用CS本身就是不明智的,那么这个说法对我来说会更有意义。信任一个处理DOM的库去跟进浏览器的变化和废弃是一回事,但当你谈论的是实际的源代码本身时,这就像是双重依赖。想象一下,在CS社区消失并转向下一个“让它更像Rails”的现象后,10年后处理遗留代码库时的情况。当一切开始出问题时,你需要找出哪些内容被废弃了,并找出如何修复CS解析器中的问题。 - Erik Reppen

12

当然可以:

hello()

`function hello() {`
console.log 'hello'
dothings()
`}`

使用反引号 ` 可以避免纯 JS。

请注意,您无法在函数体上缩进。

干杯


20
这并不表明它是用coffeescript完成的,只是coffeescript允许转到javascript。此外,这种写法很糟糕! - Mr Wilde
9
在使用前先定义术语是更糟糕的事情 xD - Zaid Daghestani
1
此外,在v8的后续版本中,函数声明似乎得到了大量优化。 - James M. Lay
请注意,您可以编写以下代码以允许缩进:function updateSettings() { dothings() }这是有关编程的内容。请将上述链接中的评论翻译成中文。 - avalanche1

6

使用CoffeeScript需要记住的一件事是,你总是可以回到JavaScript。虽然CoffeeScript不支持命名函数声明,但你总是可以返回到JavaScript来实现它。

http://jsbin.com/iSUFazA/11/edit

# http://jsbin.com/iSUFazA/11/edit
# You cannot call a variable function prior to declaring it!
# alert csAddNumbers(2,3) # bad!

# CoffeeScript function
csAddNumbers = (x,y) -> x+y

# You can call a named function prior to
# delcaring it
alert "Calling jsMultiplyNumbers: " + jsMultiplyNumbers(2,3) # ok!

# JavaScript named function
# Backticks FTW!
`function jsMultiplyNumbers(x,y) { return x * y; }`

您可以在CoffeeScript中编写一个大函数,然后使用反引号技巧让JavaScript调用其他函数:
# Coffeescript big function
csSomeBigFunction = (x,y) ->
   z = x + y
   z = z * x * y
   # do other stuff
   # keep doing other stuff

# Javascript named function wrapper
`function jsSomeBigFunction(x,y) { return csSomeBigFunction(x,y); }`

1

不,你不能在CoffeeScript中定义一个函数并让它生成一个CoffeeScript函数声明。

即使你只是写

-> 123

生成的JS将被包裹在括号中,因此成为函数表达式

(function() {
  return 123;
});

我的猜测是这是因为函数声明被“提升”到封闭作用域的顶部,这会破坏coffeescript源代码的逻辑流程。

11
正是因为hoisting(变量提升),我想使用函数声明! - ivanreese
1
CoffeeScript 在某种程度上已经“提升”了,因为它在作用域顶部使用 var 预先声明变量。因此函数可以相互引用,顺序并不重要。 - Evan Moran
15
@EvanMoran 是的,CoffeeScript确实会预先声明变量,但是函数不会被提升,因为变量一直保持未定义状态,直到函数表达式出现。因此,在定义函数之前无法使用它们。 - jasonkarns

1
虽然这是一篇旧帖子,但我想为未来的谷歌用户增加一些对话内容。
OP 是正确的,我们不能在纯 CoffeeScript 中声明函数(不包括使用反引号在 CoffeeScript 文件中转义纯 JS 的想法)。
但是我们可以将函数绑定到窗口,并最终得到类似于命名函数的东西,可以像调用函数 foo(param) 一样在代码中的某处调用。我并不是在说这是一个命名函数,我提供了一种使用纯 CoffeeScript 实现 OP 想要实际执行的操作(调用函数)的方法。
以下是一个示例,在 coffeescript 中附加到窗口的函数:
window.autocomplete_form = (e) ->
    autocomplete = undefined
    street_address_1 = $('#property_street_address_1')
    autocomplete = new google.maps.places.Autocomplete(street_address_1[0], {})
    google.maps.event.addListener autocomplete, "place_changed", ->
        place = autocomplete.getPlace()

        i = 0

        while i < place.address_components.length
            addr = place.address_components[i]
            st_num = addr.long_name if addr.types[0] is "street_number"
            st_name = addr.long_name if addr.types[0] is "route"

            $("#property_city").val addr.long_name if addr.types[0] is "locality"
            $("#property_state").val addr.short_name if addr.types[0] is "administrative_area_level_1"
            $("#property_county").val (addr.long_name).replace(new RegExp("\\bcounty\\b", "gi"), "").trim() if addr.types[0] is "administrative_area_level_2"
            $("#property_zip_code").val addr.long_name if addr.types[0] is "postal_code"
            i++

        if st_num isnt "" and (st_num?) and st_num isnt "undefined"
            street1 = st_num + " " + st_name
        else
            street1 = st_name

        street_address_1.blur()
        setTimeout (->
            street_address_1.val("").val street1
            return
            ), 10
        street_address_1.val street1
        return

这是使用Google Places返回地址信息并自动填充表单。在Rails应用程序中,我们有一个部分被加载到页面中。这意味着DOM已经创建,如果我们在初始页面加载时调用上述函数(在ajax调用渲染部分之前),jQuery将无法看到$('#property_street_address_1')元素(相信我-它没有)。因此,我们需要延迟google.maps.places.Autocomplete(),直到元素出现在页面上。我们可以通过Ajax回调来实现这一点,在部分成功加载后进行。
            url = "/proposal/"+property_id+"/getSectionProperty"
            $("#targ-"+target).load url, (response, status, xhr) ->
                if status is 'success'
                    console.log('Loading the autocomplete form...')
                    window.autocomplete_form()
                    return

            window.isSectionDirty = false

所以在这里,本质上我们做的是调用foo()函数。


1
因为“函数声明”是有害的。看看这段代码。
function a() {
        return 'a';
}

console.log(a());

function a() {
        return 'b';
}

console.log(a());

输出会是什么?
b
b

如果我们使用函数定义
var a = function() {
        return 'a';
}

console.log(a());

a = function() {
        return 'b';
}

console.log(a());

输出结果为:
a
b

9
函数声明并不具有邪恶性质。你只需要了解变量和函数声明在JS中如何被提升。更多关于变量提升函数提升的信息请参考。 - Ben Harold
函数定义比函数声明更直观。 - Tomasz Jakub Rup

0

试试这个:

defineFct = (name, fct)->
  eval("var x = function #{name}() { return fct.call(this, arguments); }")
  return x

现在以下内容将打印“true”:

foo = defineFct('foo', ()->'foo')
console.log(foo() == foo.name)

我实际上并不使用这个,但有时希望咖啡函数有反射的名称。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接