Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does coffeescript generate classes like this?

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?

like image 736
ryeguy Avatar asked Jan 12 '11 16:01

ryeguy


3 Answers

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.

like image 120
jashkenas Avatar answered Sep 22 '22 14:09

jashkenas


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

like image 21
yfeldblum Avatar answered Sep 25 '22 14:09

yfeldblum


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.

like image 42
Dave Dopson Avatar answered Sep 22 '22 14:09

Dave Dopson