CoffeeScript是最近比较流行的一个小的编程语言,它有自己的语法(受Python和Ruby影响比较多,个人觉得更象Ruby),其编译器将其编译输出javascript。至于生成的javascript则可以在浏览器运行也可以在服务器端运行(NodeJS)。例如最简单的helloWorld函数(你可以点击这个链接进入CoffeeScript提供的在线工具)
# ===========
# CoffeeScript
# ===========
helloWorld = (name)->
alert "Hello World #{name}"
// ==========
// Javascript
// ==========
var helloWorld;
helloWorld = function(name) {
return alert("Hello World " + name);
};
下面我们可以就上面的代码简单介绍CoffeeScript的语法。
简单语法
-
空格/缩进很重要
跟Ruby一样,没有分号作为语句的分隔符,而是以换行;也没有花括号来表示一个语句块,而是以缩进来表示。所以在上面的代码中
alert
语句是缩进了两个空格。当然你可以缩进三个,四个,任意个,只是同一个语句块要有同样的缩进。 -
变量不需要声明
所有的变量都会自动定义成局部变量,以消除全局变量。例如上面例子的helloWorld函数。熟悉javascript的都知道,没有var声明的变量,自动成为全局变量,那样容易造成变量名的混乱。而不声明var是可能发生的手误,尤其对于新手来说。所以如果想要定义全局变量,习惯上可能会以
g_
作为前缀,这样便于代码的阅读和维护。当然现在的Javascript讲究的是尽量不要用全局变量,最大的理由就是变量的覆盖,尤其是在采用了很多类库的情况下,如果大家都用全局变量就完蛋了。举个例子来说,大家都知道大名鼎鼎的$
,JQuery的简写。如果我自己的代码也定义一个全局变量为$
,并且加载在jQuery之后,那么就覆盖了jQuery的$
,你再想用它就不是jQuery了。 -
函数声明采用箭头(->)
究其原因是没有的,反正语法就是这么规定,所以需要记住。类似与上面的例子,在箭头后面我们定义了函数的实现。至于函数的参数需要在箭头的左边声明,并且置于括号之中。如果没有参数的话,你可以在括号中置空,也可以连括号都不要。CoffeeScript支持设置默认参数,可变参数,具体可以参考CoffeeScript。
-
函数调用可以不要括号
你可以看到alert函数调用是没有加括号的,这也是Ruby里面的习惯用法。这样更佳符合英文的书写和阅读,对于我们中国人来说可能有括号更加容易理解,至少象我这样从C语言过来的。但是要注意,如果一个函数没有参数的话,那么调用的时候一定要加括号,应为CoffeeScript它不确定你是当当要引用这个函数还是要调用它。其实Ruby是默认认为调用。这里也可以看到CoffeeScript只是实现了Javascript的一个子集,因为它也可以调用alert方法。
-
双引号的字符串中可插值
在调用alert时候生成的字符串,我们直接在字符串中引入了变量name。这个方法还是很不错的。注意一定是双引号的字符串,单引号是不行的。
-
函数返回值默认是最后一句的值
在生成的javascript版本helloWorld,你可以发现有return语句,尽管我们在CoffeeScript中并没有return。这就是默认的,CoffeeScript函数返回最后一句的值。有的时候这减少了写return了。但是如果在函数中间要返回值,那还是得要return的。
class
其实我最喜欢的是类的支持。jQuery这么流行的库为什么不支持面向对象呢?我认为基本的面向对象就是类,类的继承,方法的覆盖。这些CoffeeScript都给出了很好的支持。譬如,
class Animal
@count: 0
@getCount: ->
@count
constructor: (@name)->
Animal.count++
getName: ->
@name
say: ->
alert @getName()
class Dog extends Animal
constructor: (name)->
super(name)
@type = 'Dog'
getName: ->
@type + ": " + super()
animal = new Animal('A')
# alert('A')
animal.say()
# 1
alert Animal.getCount()
# alert('Dog: D')
dog = new Dog('D')
dog.say()
# 2
alert Animal.getCount()
# 0
alert Dog.getCount()
# error, no method getCount for animal
alert animal.getCount()
# error, no method getCount for dog
alert dog.getCount()
对应输出的Javascript则是
var Animal, Dog, animal, dog,
__hasProp = {}.hasOwnProperty,
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
Animal = (function() {
Animal.count = 0;
Animal.getCount = function() {
return this.count;
};
function Animal(name) {
this.name = name;
Animal.count++;
}
Animal.prototype.getName = function() {
return this.name;
};
Animal.prototype.say = function() {
return alert(this.getName());
};
return Animal;
})();
Dog = (function(_super) {
__extends(Dog, _super);
function Dog(name) {
Dog.__super__.constructor.call(this, name);
this.type = 'Dog';
}
Dog.prototype.getName = function() {
return this.type + ": " + Dog.__super__.getName.call(this);
};
return Dog;
})(Animal);
animal = new Animal('A');
animal.say();
alert(Animal.getCount());
dog = new Dog('D');
dog.say();
alert(Animal.getCount());
alert(Dog.getCount());
alert(animal.getCount());
alert(dog.getCount());
从这个代码的对比来看,CoffeeScript的确是相当高效的.具体说来class实现有如下要点
-
关键字class声明一个对象
在对象内部的定义,则是采用了函数名后面加冒号(不是等号),然后再是函数的定义。
-
constructor为构造函数 new一个对象的时候就会调用该函数,相当于Ruby中的initialize函数。显然每个class只有一个构造函数。在成员函数中的@为this的简写,例如getName函数中的@name表示就是this.name,也就是类实例的name变量。在constructor中我们采用了一个更加简单的写法,就是直接将传入的name参数赋给成员变量name,所以我们直接在参数name前面加了@符号。
-
super 子类的方法可以通过super调用父类的方法,例如Dog的constructor和getName,都有这样的用法。
-
成员函数或变量声明前加@表示静态方法或变量 例如getCount和count,它们都是直接转化成Animal的属性。所以在调用getCount的时候,只有通过Animal.getCount()才起作用。通过实例animal和dog进行调用,都会报错说方法不存在。而通过Dog类调用的时候,你会发现该方法是存在的,但是值可能不是你想要的。这是因为CoffeeScript生成的 JS代码__extends会将父类的所有属性复制一份到子类,也就是说在最开始的时候会将Animal的count和getCount属性复制到Dog里面去。而我们在Animal的构造函数中,只是增加了Animal.count的计数,而不是Dog.count的计数。
CoffeeScript的负面评价
更多其它的细节可以在CoffeeScript找到。在好评如潮的情况下,我也找到一些负面的评价。如这两篇文章,
http://ryanflorence.com/2011/case-against-coffeescript/
http://ceronman.com/2012/09/17/coffeescript-less-typing-bad-readability/
我觉得写得相当不错。我是不太喜欢There is more than one way to do it (TIMTOWTDI) (不止一种方法来实现一个功能),因为这样势必造成学习曲线增加,难记,也导致难以维护。每个人用不同的方法来做,那维护的人就要痛苦死了。而Java就是因为简单所以才会有这么多人使用,开发效率也才会高,才会容易阅读和维护。
安装CoffeeScript
最后介绍一下如何在本地安装CoffeeScript,虽然在官网都有,而且也可以在官网找到在线工具,但是为了文章的完整性,在最后还是附上。
- 安装NodeJS
npm install -g coffee-script
运行安装coffee scriptcoffee -o lib/ -cw src/
这个表示运行自动监控src下面的".coffee"文件,然后以相同的文件名编译输出到lib目录下面,只是文件名后缀为js。