CoffeeScript 简介
多年来,JavaScript 在开发者中声名狼藉。 当然,当我们需要在浏览器中做一些动态的事情时,我们会使用它,但我们也会尽可能地避免它。 正如我(和其他人)在过去一年中多次撰文所述,JavaScript 正在经历一场多维的复兴。 它的执行速度很快,并且在浏览器和实现中越来越标准化和稳定。 您可以在浏览器端应用程序的传统角色中使用它,可以单独使用,也可以使用诸如 jQuery 之类的框架。 您甚至可以使用像 Backbone.js 这样的系统在浏览器中构建整个 MVC 应用程序,正如我在过去两个月里在这个专栏中描述的那样。 在服务器端,您可以使用 node.js 来创建高性能应用程序。
JavaScript 在许多方面得到改进,这真是太好了。 同时,该语言包含许多遗留问题——不是在功能方面,而是在语法和文法方面。 您可以使用 JavaScript 做很多很棒的事情,但是很容易编写出具有意外副作用的代码,其变量不具有您期望的作用域,或者其函数的运行方式与您的意图略有不同,从而导致问题。
(另一个没有改善的事情是它的名称,以及虽然每个人都称它为 “JavaScript”,但它正式的名称是 “ECMAScript”,以批准它的标准机构命名。)
解决这个问题更有趣的方案之一是引入 JavaScript 的超集语言。 最著名的例子之一是 Objective-J,它将类似 Objective-C 的语法、对象结构和类库绑定到 JavaScript 上。 用于浏览器内 MVC 应用程序的 Cappuccino 框架是用 Objective-J 编写的,并提供了类似于 Macintosh 的 Cocoa 平台的编程体验。 另一种解决方案是在 JavaScript 代码上运行预处理器,例如 Google Closure。 Closure 将 JavaScript 编译成 JavaScript,但在这样做的过程中,它会优化和最小化您的 JavaScript 代码。
Jeremy Ashkenas(顺便说一下,他也是 Backbone.js 框架的创建者)采取了另一种方法,即创建一种全新的语言,该语言可以编译为 JavaScript。 这种他称为 CoffeeScript 的语言,不是设计为直接运行的(尽管您可以使用合适的工具来做到这一点)。 相反,CoffeeScript 代码被编译成 JavaScript 代码,然后执行。
为什么要将程序编译成 JavaScript? 首先,因为现代 JavaScript 引擎非常快。 编译为 JavaScript 可以确保执行速度很快,并且它可以在各种平台和许多上下文中工作——在许多方面类似于编译为 JVM 字节码的越来越多的语言(例如,Clojure 和 Scala)。 其次,如果可以编译为 JavaScript,则可以利用已经适用于 JavaScript 的库和框架。
使用 JavaScript 之外的语言,但可以编译为 JavaScript 的另一个原因是,您可以使用更易于访问且不易出错的语法。 我一直是 Ruby 和 Python 的长期用户,我发现 CoffeeScript 融合了这两种语言中的许多我最喜欢的功能,从而创建了一种具有易于理解的语法和强大功能的语言。 如果您了解 Ruby、Python 和 JavaScript,您很可能可以在相对较短的时间内学习这种语言。
现在,确实如此,在撰写本文时,CoffeeScript 已经存在了大约 18 个月,并且已经吸引了很多粉丝。 但是,在过去的几个月中,一些事件使它变得更加有趣。 首先,Mozilla 的 CTO 兼 JavaScript 的发明者 Brendan Eich 今年早些时候表示,CoffeeScript 很可能为下一版本的 JavaScript(称为 “Harmony”)提供一些有用的语法思路。 因其关于 JavaScript 的想法、写作和讲座而闻名的 Douglas Crockford 显然也支持 CoffeeScript。
从我的角度来看,更重要的是,Ruby on Rails 核心团队宣布 CoffeeScript 将成为 Rails 的标准部分,从 3.1 版本开始。(3.1 版本还将 jQuery 作为标准框架,替换 Prototype,并加入了 SASS,一种 CSS 宏语言。)我不会在这里探讨 CoffeeScript 和 Rails 的细节,但是一个庞大、不断增长、活跃且有影响力的 Web 框架采用 CoffeeScript 的事实,将使像 Eich 这样的 JavaScript 语言架构师有机会看到它如何在 “野外” 工作,并在决定将什么(以及不将什么)放入下一版本的 JavaScript 之前收集开发人员的反馈。
因此,这个月,我想介绍一些 CoffeeScript 的基础知识。 我必须承认,我对学习另一种新语言持怀疑态度,尤其是编译为 JavaScript 的语言。 但是经过一些玩耍和实验后,我明白了为什么人们对它如此兴奋。 CoffeeScript 在未来几年将处于什么位置还有待观察,但是现在它肯定是一个有用的工具,特别是如果您正在开发 Web 应用程序,其代码已经变得越来越扭曲,并且 JavaScript 函数变得越来越复杂。
安装和运行
您可以通过多种方式下载和安装 CoffeeScript。 一种方法是从存储和开发它的 GitHub 帐户下载并编译源代码。 只要说
git clone http://jashkenas.github.com/coffee-script/
您将在机器上拥有源代码。 CoffeeScript 编译器依赖于 node.js,因此您也可以使用 node.js 包管理器 npm 下载并安装它,这并不奇怪
npm install coffee-script
最后,对于那些希望将 CoffeeScript 与其他软件包一起安装,并处理依赖关系的人来说,存在一些(各种风格的)Linux 软件包。
安装完成后,您可以调用交互式命令行提示符,并在 coffee>
提示符下开始编码。
您可以使用内置的 console.log 函数立即打印一些东西
coffee> console.log "Hello, world"
什么是 console.log? 它是 “console” 对象上的内置函数——例如
coffee> console.log.toString() console.log.toString() function () { process.stdout.write(format.apply(this, arguments) + '\n'); }
如您所见,这使您可以查看幕后的 JavaScript。 如果您对 “console” 对象执行相同的操作,您会看到一些更像您熟悉和喜爱的 JavaScript(或者不喜欢)
coffee> console.toString() console.toString() [object Object]
在某些方面,我还没有做任何与标准 JavaScript 不同的事情。 毕竟,JavaScript 就是关于函数的——您可以将其作为参数传递的函数,以及可以使用括号执行的函数,就像在 Python 中一样。 但是,一旦您定义了一个函数,您就可以开始看到差异
coffee> hello_world = (name) -> console.log "Hello there, #{name}!"
首先,您可以看到在 CoffeeScript 中定义函数实际上是一个变量赋值。 函数定义语法按照我的标准仍然有点奇怪,使用 ->
分隔匿名函数的参数和主体。 参数在括号中命名,必要时用逗号分隔。
在这个特定的函数主体中,我还使用 Ruby 风格的字符串插值来打印用户的姓名。 如果您厌倦了使用 + 或第三方库来插入字符串值,这很可能是一个对您有用的功能。
基本语法
要执行该函数,只需将该函数的名称提供给 CoffeeScript REPL(读取-求值-打印循环),后跟括号(即执行)
coffee> hello_world()
CoffeeScript 函数返回其最终值,就像 Ruby 方法一样。 在此 hello_world 函数的情况下,这意味着该函数返回 “undefined”。 您可以通过让该函数返回字符串而不是打印字符串来使其更好一些
coffee> hello_world = (name) -> "Hello there, #{name}!"
然后,您可以说
coffee> console.log hello_world('Reuven') Hello there, Reuven!
或者
coffee> hello_world('Reuven') 'Hello there, Reuven!'
取决于您是否想要获取字符串或只是将其打印到控制台。
CoffeeScript 提供了基本的 JavaScript 数据类型,但它添加了大量的语法糖以及额外的方法,使这些数据类型更容易处理。 例如,CoffeeScript 使用 Python 风格的三引号字符串。
“existential” 运算符,问号 (?),允许您确定变量是否包含除 null 或 undefined 值之外的其他内容。 这比仅仅检查值的真实性更好,如果值包含 0 或空字符串,则会导致(字面上的!)假阴性。 例如
v = 0 if v console.log "yes" else console.log "no" if v? console.log "yes" else console.log "no"
上面的代码示例展示了 CoffeeScript 如何实现 if/else 控制块。 还有一个 “unless” 运算符,它(正如您可能期望的那样)反转来自 if
的输出。 CoffeeScript 还提供了后缀 “if” 和 “unless” 语句——您可能会在 Perl 和 Ruby 中看到。
您还可以看到上面的 CoffeeScript 的另一个元素,这个是从 Python 中提取的。 空格和缩进很重要,并且允许您删除许多其他语言的大括号和 begin/end。 但是,与 Python 不同,在 “if” 和 “else” 语句之后不需要冒号。
CoffeeScript 数组类似于 JavaScript 数组,但添加了各种漂亮的语法。 想要一个十元素数组? 只需使用此
a = [0..9]
您将获得它,使用内置的 “range” 运算符。(但这不适用于字母。)您也可以检索范围
a[5..7]
您可以分配给一个范围,而无需担心您要拼接的精确长度
a[5..7] = ['a', 'b']
您也可以使用此语法检索子字符串
coffee> alphabet[5..10] alphabet[5..10] 'fghijk'
CoffeeScript 字符串,就像 JavaScript 字符串一样,是不可变的。 因此,您不能分配给子字符串
coffee> alphabet[5..10] = [5] alphabet[5..10] = [5] [ 5 ] coffee> alphabet alphabet 'abcdefghijklmnopqrstuvwxyz'
JavaScript 对象,可以像哈希(又名 “字典” 或 “关联数组”)一样使用,其工作方式与 JavaScript 中相同,但具有更简单的语法。 例如,在 JavaScript 中,您可以编写
person = { first_name: 'Reuven', last_name: 'Lerner' }
在 CoffeeScript 中,您可以说
person = first_name: 'Reuven' last_name: 'Lerner'
再一次,使用 Python 风格的缩进而不是大括号使其更紧凑,但同样易于阅读。
循环和推导式
您可以使用 for..in 循环遍历数组
for number in a console.log number
我应该指出的是,这将在单独的一行上打印每个数字,并且将返回一个未定义值的数组(因为 console.log 不返回值,并且 “for” 返回一个数组)。
可以使用类似的 for..of 循环访问对象
for key, value of person console.log "key = '#{value}'"
请注意,for..of 循环适用于任何 JavaScript 对象。 因为每个对象都可以包含属性,并且因为数组是对象,所以您甚至可以为数组分配属性
a.foo = 'bar'
使用 for..in 循环,您将继续在 “a” 数组中看到这些值。 但是使用 for..of 循环,您不仅会获得数组元素(其键是索引),还会获得 “foo”。 此外,请注意,如果您仅对对象上定义的属性感兴趣,而不是对其原型感兴趣,则可以添加 “own” 关键字
for own key, value of person console.log "key = '#{value}'"
在这种特殊情况下,不会有任何区别。 但是,如果 “person” 要有一个原型,并且该原型有自己的键,您将看到原型的键和值,而不仅仅是 “person” 对象的键和值。
但是,由于推导式的存在,对这些 for..in 和 for..of 循环的需求大大降低,推导式的想法来自 Python,需要一些时间来适应,但是一旦您适应了它,它会提供极大的力量和简洁性,让您可以将 map、reduce 和 filter 组合在一个语句中。 例如,您可以获取一个数字列表
coffee> a = [100..110] [ 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110 ]
然后您可以将每个数字乘以 5,就像您在 Ruby 或 Python 中使用 “map” 一样,但是使用 “推导式” 语法
coffee> i*5 for i in a [ 500, 505, 510, 515, 520, 525, 530, 535, 540, 545, 550 ]
您还可以选择出一些值。例如,假设您只想对那些奇数元素进行操作。您可以这样说:
coffee> i*5 for i in a when i%2 [ 505, 515, 525, 535, 545 ]
对象
正如我上面所写的,JavaScript(以及 CoffeeScript)中的一切都是对象,并且每个对象都有属性。(另一种考虑方式是,JavaScript 中的一切都是哈希,具有键值对。)理解和使用这一点起初可能会有些令人困惑,特别是由于 "this" 关键字的运作方式。CoffeeScript 提供了几个解决方案来解决这个问题,以及那些让 JavaScript 程序员抓狂的作用域和变量问题。
首先,所有变量的作用域都是词法的,并且从缩进中可以明显看出。您可以将变量附加到全局对象或其他对象,但只有当您想要这样做时才会这样做,而不是因为错误地忘记在某个地方添加 "var"。
其次,可以像访问和分配变量一样访问和分配属性,使用 Ruby 风格的 @varname 语法。因此,当您在 CoffeeScript 程序中说 x=5
时,您是将值 5 赋值给词法变量 x。但是,当您说 @x=5
时,您是将值 5 赋值给当前对象的属性 x。
最后,"this" 在 JavaScript 中是动态作用域的,其值会改变以反映当前函数所附加到的对象。这意味着如果您不小心,您可以编写引用 "this" 的回调函数,但最终却意外地引用了错误的对象。CoffeeScript 允许您不仅可以使用 ->
(“细箭头”)定义函数,还可以使用 =>
(“粗箭头”)。区别在于,当您使用 =>
定义函数时,它们会绑定到定义时 "this" 的值,从而允许使用 @propname 语法访问定义上下文的属性。
所有这些东西都在 CoffeeScript 的对象模型中结合在一起,该模型扩展了 JavaScript 的对象模型,以便您可以处理类似于传统类实例模型的东西,而不是内置的、JavaScript 风格的基于原型的模型。原型仍然存在并且有效——事实上,CoffeeScript 的类编译成 JavaScript 原型。但是,您可以通过声明带有构造函数的类来超越原型风格的继承。这是一个您可以做的事情的简单例子:
class Person constructor: (firstName='NoFirst', lastName='NoLast') -> @firstName = firstName @lastName = lastName Person.count++ @count: 0 p1 = new Person() console.log p1 console.log Person.count p2 = new Person('Reuven') console.log p2 console.log Person.count p3 = new Person('Reuven', 'Lerner') console.log p3 console.log Person.count
当您运行上面的文件时,您会得到以下输出:
{ firstName: 'NoFirst', lastName: 'NoLast' } 1 { firstName: 'Reuven', lastName: 'NoLast' } 2 { firstName: 'Reuven', lastName: 'Lerner' } 3
这不仅展示了您如何以类似于传统类的方式使用 CoffeeScript 类,还展示了您可以通过在 ->
符号之前声明函数参数的值来为函数参数设置默认值。您还可以看到 @propname 语法的使用如何自然地融入这个对象模型。上面的构造函数看起来几乎像一个 Ruby 方法,而不是一个 JavaScript 方法,在局部变量和实例变量之间有明显的区别。
结论
CoffeeScript 是一种有吸引力的新语言,它使 JavaScript 编程变得有趣和容易。您可以将其视为具有新语法的 JavaScript,或是一种易于学习的语言,可以集成到 JavaScript 应用程序中,并从 Python 和 Ruby 中汲取了许多想法。它消除了与传统 JavaScript 相关的许多语法膨胀和问题,同时提供了一种比原始的、未经优化的 JavaScript 更易于编写和维护,但也更快执行的语言。CoffeeScript 是一种我们都应该在未来几个月和几年内关注的语言。它可能不会完全取代 JavaScript,但它肯定会使完成某些任务变得更容易。
下个月,我将介绍如何将 CoffeeScript 集成到您的浏览器端 Web 应用程序中,特别是那些使用 jQuery 的应用程序。这似乎是 Ruby on Rails 社区在其 3.1 版本中朝着这个方向发展的趋势,即使您最喜欢的框架没有采用 CoffeeScript,了解它们如何协同工作可能也会很有用。
资源
CoffeeScript 的主页,包括文档、快速参考、常见问题解答和带有注释的源代码,位于 http://jashkenas.github.com/coffee-script。有一个活跃且不断增长的 CoffeeScript 用户社区,在 GitHub 上有一个 IRC 频道 (#coffeescript) 和 Wiki。
有关 CoffeeScript 的良好介绍,请参阅 Jacques Crocker 编写的演示文稿,可在 http://coffeescript-seattlejs.heroku.com 上找到。
最后,Pragmatic Programmers 发布了(在撰写本文时)一本由活跃的 CoffeeScript 用户 Trevor Burnham 编写的优秀预发布 "beta book"。如果您有兴趣了解更多关于这种有趣的小语言,我强烈推荐这本书。它主要面向初学者,但鉴于目前高级 CoffeeScript 程序员的数量有限,这不应该困扰您。