【问题标题】:Why does coffeescript generate classes like this?为什么 coffeescript 会生成这样的类?
【发布时间】:2011-01-12 16:16:36
【问题描述】:

给定以下咖啡脚本代码:

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

这是生成的:

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;
})();

但是为什么没有生成更惯用的代码呢?

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

我知道coffeescript 在匿名函数中包装了很多东西来控制范围泄漏,但是这里可能会泄漏什么?

【问题讨论】:

  • 主观且有争议的近距离投票?真的吗?这是一个有效的问题,询问为什么有必要将函数包装在匿名函数中。

标签: javascript coffeescript


【解决方案1】:

生成的代码可以在 Internet Explorer 中可靠地使用命名函数。 (在本例中为“Animal”。)如果您只是在*范围内使用命名函数,它将与可能存在的任何 var Animal = 声明冲突......即使在较低范围内,也会阻止它们被正确引用.为了解决 IE 错误,我们在类定义周围包含函数包装器。

【讨论】:

  • “名称冲突”并不完全是问题(无论如何你正在做var Animal =)。问题是您不应该为 JScript 命名函数 expressions。更多关于“IE bug”的信息:kangax.github.com/nfe
【解决方案2】:

这是为了支持回溯,包括类名,而不仅仅是抛出异常时的函数名。

【讨论】:

  • @casablanca -- 参见:javascriptkata.com/2010/05/19/… 这是在堆栈跟踪中看到 aonon(), aonon()func1(), func2() 的区别。
  • @Sean:这解释了为什么函数被命名为Animal,但似乎没有解释为什么它被包裹在一个匿名函数中。
  • @Sean Vieira:我在 OP 的代码中没有看到任何命名的匿名函数。
  • @casablanca -- 查看 jashkenas 的回答:*.com/questions/4671072/…(这就是我喜欢 * 的原因)。
  • -1。那是错的。 JS 没有“类”的概念。堆栈跟踪将具有函数名称“Animal”,并且对于任何实例方法,因为它是在 Animal 对象(this.prototype.constructor == Animal)的上下文中调用的,所以堆栈将在适当的高级浏览器上反映这一点。这些都与关闭无关。
【解决方案3】:

CoffeeScript 方法在缩小方面也有优势。

来自my other answer

对于大多数合理的类,CoffeeScript 生成的闭包会产生更小的缩小输出。 闭包包装器减少了 25 个字节的开销,但它使您免于重复类名,节省了 k * N 个字节(k=letters-in-name, N=num参考文献)。 例如,如果像 BoilerPlateThingyFactory 这样的类有 2+ 个方法,则闭包包装器会生成更小的压缩代码。



更详细...

使用闭包生成的 Coffee 代码缩小为:

// 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 的替代“惯用”实现缩小为:

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

注意“动物”这个名字在 Coffee 形式中精确存在一次,而在 ryeguy 的“惯用”变体中出现 N=2 次。现在“Animal”只有 6 个字母,而且只有 1 个方法,所以这里的 Coffee 应该会丢失 25-6 = 19 个字节。咨询我的缩小代码,它是 138 字节到 119 字节,增量为 ... 19 字节。再增加4个方法,优势会切换到Coffee。这不仅仅是方法;类常量和其他 ref 类型也很重要。

【讨论】:

  • 丑化代码中逗号分隔的return 表达式很有趣。在我的控制台中,return x, y, z; 似乎评估了所有三个表达式,但只返回最后一个。除了让代码更“丑”之外,这样做有什么效果吗?
  • @JustinMorgan - 您先生发现了“逗号运算符”!它就像一个分号,但更强大,因为它可以用作表达式。所以“a,b,c;”将评估“a”,然后是“b”,然后是“c”,整个表达式将具有来自“c”的值。它是 JS 继承自 C 的运算符,fwiw。 en.wikipedia.org/wiki/Comma_operator