7

Given the following coffeescript code:

class Animal
  constructor: (@name) ->
  speak: (things) -> "My name is #{@name} and I like #{things}"

This is generated:

var Animal = (function() {
  function Animal(name) {
    this.name = name;
  }
  Animal.prototype.speak = function(things) {
    return "My name is " + this.name + " and I like " + things;
  };
  return Animal;
})();

But why isn't this more idiomatic code generated?

var Animal = function Animal(name) {
  this.name = name;
};
Animal.prototype.speak = function(things) {
  return "My name is " + this.name + " and I like " + things;
};

I know that coffeescript wraps a lot of stuff in anonymous functions to control scope leak, but what could leak here?

ryeguy
  • 65,519
  • 58
  • 198
  • 260
  • 3
    A subjective and argumentative close vote? Really? This is a valid question asking why it's necessary to wrap the functions in an anonymous function. – ryeguy Jan 12 '11 at 16:19

3 Answers3

12

The generated code makes it possible to reliably have named functions in Internet Explorer. (In this case, "Animal".) If you simply use a named function at top-level scope, it will conflict with any var Animal = declarations that might be present ... even in lower scopes, preventing them from being referenced correctly. To work around the IE bug, we include the function wrapper around the class definition.

jashkenas
  • 3,377
  • 24
  • 16
  • 5
    "Name confliction" isn't exactly the concern (you're doing `var Animal =` anyway). The issue is that you should never have named function _expressions_ for JScript. More about "the IE bug": http://kangax.github.com/nfe/ – matyr Jan 12 '11 at 19:31
2

The CoffeeScript method also has advantages for minification.

From my other answer:

For most reasonable classes, the closure generated by CoffeeScript generates smaller minified output. The closure wrapper is 25 bytes of minified overhead, but it saves you from repeating the classname, saving k * N bytes (k=letters-in-name, N=num-of-refs). e.g., if a class like BoilerPlateThingyFactory has 2+ methods, the closure wrapper generates smaller minified code.



in more detail...

The Coffee generated code using a closure minifies to:

// Uglify '1.js' = 138 bytes (197 w/ whitespace):

var Animal=function(){function e(e){this.name=e}return e.prototype.speak=function(e){return"My name is "+this.name+" and I like "+e},e}();

// with whitespace ("uglifyjs -b"):

var Animal = function() {
    function e(e) {
        this.name = e;
    }
    return e.prototype.speak = function(e) {
        return "My name is " + this.name + " and I like " + e;
    }, e;
}();

ryeguy's alternative "idiomatic" implementation minifies to this:

// Uglify '2.js' = 119 bytes (150 w/ whitespace):

var Animal=function(t){this.name=t};Animal.prototype.speak=function(e){return"My name is "+this.name+" and I like "+e};

// with whitespace ("uglifyjs -b"):

var Animal = function(t) {
    this.name = t;
};

Animal.prototype.speak = function(e) {
    return "My name is " + this.name + " and I like " + e;
};

Notice how the name "Animal" name exists precisely once in the Coffee form, and N=2 times in ryeguy's "idiomatic" varient. Now "Animal" is only 6-letters, and there's only 1 method, so Coffee here should lose by 25-6 = 19 bytes. Consulting my minified code, it's 138 bytes to 119 bytes, for a delta of ... 19 bytes . Add 4 more methods, and the advantage will switch to Coffee. And it's not just methods; class constants and other ref types count too.

Community
  • 1
  • 1
Dave Dopson
  • 41,600
  • 19
  • 95
  • 85
  • The comma-separated `return` expression in your uglified code is interesting. In my console, `return x, y, z;` seems to evaluate all three expressions, but only return the last one. Is there any effect from doing it this way, other than making the code more "ugly"? – Justin Morgan - On strike Mar 15 '13 at 16:27
  • @JustinMorgan - You sir have discovered the "comma operator"! It's like a semi-colon, but more powerful since it can be used as an expression. so "a,b,c;" will evaluate "a", then "b", then "c", and the overall expression will have the value from "c". It's an operator that JS inherits from C, fwiw. http://en.wikipedia.org/wiki/Comma_operator – Dave Dopson Mar 15 '13 at 20:17
2

This is to support backtraces including the class names and not just the function names when an exception is thrown.

yfeldblum
  • 65,165
  • 12
  • 129
  • 169
  • @casablanca -- see: http://www.javascriptkata.com/2010/05/19/how-to-de-anonymize-your-anonymous-functions/ It's the difference between seeing `aonon(), aonon()` and `func1(), func2()` in the stack trace. – Sean Vieira Jan 12 '11 at 16:49
  • @Sean: That explains why the function is named `Animal`, but doesn't seem to explain why it's wrapped in an anonymous function. – ryeguy Jan 12 '11 at 16:58
  • @Sean Vieira: I don't see any named anonymous functions in the OP's code. – casablanca Jan 12 '11 at 17:00
  • @casablanca -- see jashkenas' answer: http://stackoverflow.com/questions/4671072/why-does-coffeescript-generate-classes-like-this/4672117#4672117 (This is why I love StackOverflow). – Sean Vieira Jan 12 '11 at 18:32
  • -1. that's just wrong. JS has no concept of a "class". The stack trace will have the function name "Animal", and for any instance method, because it's called in the context of an Animal object (this.prototype.constructor == Animal), the stack will reflect this on a suitably advanced browser. None of this is related to the closure. – Dave Dopson Aug 18 '12 at 01:48