Function declaration in CoffeeScript

I notice that in CoffeeScript, if I define a function using:

a = (c) -> c=1

I can only get the function expression:

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

But, personally I often use function declaration,for example:

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

I do use the first form, but I'm wondering if there is a way in CoffeeScript generating a function declaration. If there is no such way, I would like to know why CoffeeScript avoid doing this. I don't think JSLint would holler an error for declaration, as long as the function is declared at the top of the scope.

38886 次浏览

CoffeeScript uses function declarations (aka "named functions") in just one place: class definitions. For instance,

class Foo

compiles to

var Foo;
Foo = (function() {
function Foo() {}
return Foo;
})();

The reason CoffeeScript doesn't use function declarations elsewhere, according to the FAQ:

Blame Microsoft for this one. Originally every function that could have a sensible name retrieved for it was given one, but IE versions 8 and down have scoping issues where the named function is treated as both a declaration and an expression. See this for more information.

In short: Using function declarations carelessly can lead to inconsistencies between IE (pre-9) and other JS environments, so CoffeeScript eschews them.

No, you can't define a function in coffee script and have it generate a function declaration in coffee script

Even if you just write

-> 123

the generated JS will be wrapped in parens, thus making it a function expression

(function() {
return 123;
});

My guess is that this is because function declarations get "hoisted" to the top of the enclosing scope which would break up the logical flow of the coffeescript source.

One thing to keep in mind with CoffeeScript is that you can always kick back to JavaScript. While CoffeeScript doesn't support named function declarations, you can always drop back to JavaScript to do it.

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; }`

You can also write a big fat function in CoffeeScript and then just use the backticks trick to have JavaScript call the other function:

# 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); }`

Yes you can:

hello()


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

You escape pure JS via the backtick `

Note that you can't indent on your function body.

Cheers

While this is an older post, I wanted to add something to the conversation for future Googlers.

OP is correct in that we cannot declare functions in pure CoffeeScript (excluding the idea of using back-ticks to escape pure JS inside the CoffeeScript file).

But what we can do is bind the function to the window and essentially end up with something we can call as though it was a named function. I am not stating this is a named function, I'm providing a way to do what I imagine OP wants to actually do (call a function like foo(param) somewhere in the code) using pure CoffeeScript.

Here is an example of a function attached to the window in 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

This is using Google Places to return address information to auto-populate a form.

So we have a partial in a Rails app which is being loaded into a page. This means the DOM is already created, and if we call the function above on initial page load (before the ajax call renders the partial), jQuery won't see the $('#property_street_address_1') element (trust me - it didn't).

So we need to delay the google.maps.places.Autocomplete() until after the element is present on the page.

We can do this via the Ajax callback on successful load of the partial:

            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

So here, essentially, we're doing the same thing as calling foo()

Try this:

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

Now the following will print "true":

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

I don't actually use this, but do sometimes wish coffee functions had names for introspection.

Why? Because function declaration is evil. Look at this code

function a() {
return 'a';
}


console.log(a());


function a() {
return 'b';
}


console.log(a());

What will be on the output?

b
b

If we use the function definition

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


console.log(a());


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


console.log(a());

the output is:

a
b