官网文档传送门
CoffeeScript 是一门编译到 JavaScript 的小巧语言. 在 Java 般笨拙的外表下, JavaScript 其实有着一颗华丽的心脏. CoffeeScript 尝试用简洁的方式展示 JavaScript 优秀的部分.
CoffeeScript 的指导原则是: “她仅仅是 JavaScript”. 代码一一对应地编译到 JS, 不会在编译过程中进行解释. 已有的 JavaScript 类库可以无缝地和 CoffeeScript 搭配使用, 反之亦然. 编译后的代码是可读的, 且经过美化, 能在所有 JavaScript 环境中运行, 并且应该和对应手写的 JavaScript 一样快或者更快.
换而言之,CoffeeScript只是将JS给简化,使得JS的编码更加人性化。虽然CoffeeScript编译出来的JS看起来并不简单,但CoffeeScript编写的时候确实会减少不少代码量。具体可参照后文的例子。
CoffeeScript可以通过npm安装和管理。首先你要安装nodeJS,然后通过以下命令安装:
npm install -g coffee-script
安装之后, 你应该可以运行 coffee
命令以执行脚本, 编译 .coffee 文件到 .js 文件, 和提供一个交互式的 REPL. coffee
命令有下列参数:
参数 | 作用 |
---|---|
-c, –compile | 编译一个 .coffee 脚本到一个同名的 .js 文件 |
-m, –map | 随 JavaScript 文件一起生成 source maps. 并且在 JavaScript 里加上 sourceMappingURL 指令 |
-i, –interactive | 启动一个交互式的 CoffeeScript 会话用来尝试一些代码片段. 等同于执行 coffee 而不加参数. |
-o, –output [DIR] | 将所有编译后的 JavaScript 文件写到指定文件夹. 与 –compile 或 –watch 搭配使用. |
-j, –join [FILE] | 编译之前, 按参数传入顺序连接所有脚本到一起, 编译后写到指定的文件. 对于编译大型项目有用. |
-w, –watch | 监视文件改变, 任何文件更新时重新执行命令. |
-p, –print | JavaScript 直接打印到 stdout 而不是写到一个文件. |
-s, –stdio | 将 CoffeeScript 传递到 STDIN 后从 STDOUT 获取 JavaScript. 对其他语言写的进程有好处. 比如:cat src/cake.coffee coffee -sc |
-l, –literate | 将代码作为 Literate CoffeeScript 解析. 只会在从 stdio 直接传入代码或者处理某些没有后缀的文件名需要写明这点. |
-e, –eval | 直接从命令行编译和打印一小段 CoffeeScript. 比如:coffee -e "console.log num for num in [10..1]" |
-b, –bare | 编译到 JavaScript 时去掉顶层函数的包裹. |
-t, –tokens | 不对 CoffeeScript 进行解析, 仅仅进行 lex, 打印出 token stream: [IDENTIFIER square] [ASSIGN =] [PARAM_START (] … |
–nodejs | node 命令有一些实用的参数, 比如--debug , --debug-brk , --max-stack-size , 和 --expose-gc . 用这个参数直接把参数转发到 Node.js. 重复使用 --nodejs 来传递多个参数. |
-n, –nodes | 不对 CoffeeScript 进行编译, 仅仅 lex 和解析, 打印 parse tree: |
Expressions
Assign
Value "square"
Code "x"
Op *
Value "x"
Value "x"
coffee的函数通过一组可选的圆括号包裹的参数, 一个箭头, 一个函数体来定义
square = (x) -> x * x
等同于
square = function(x) {
return x * x;
};
函数体的最后一条表达式会作为返回值返回。
如果不需要参数,则可以省略参数列表,表示为
square = -> 'empty'
相当于
square = function() {
return 'empty';
};
如果需要多个参数,只需要按(参数1,参数2…)的形式写就好。
参数可以设置默认值,例如
add = (x = 4, y = 3) -> x + y
add 5
等同于
add = function(x, y) {
if (x == null) {
x = 4;
}
if (y == null) {
y = 3;
}
return x + y;
};
add(5);
函数还有另一种表示方法,就是用=>
代替->
,两者的区别在于前者会解释成闭包函数
square = (x) => x * x
解释为:
square = (function(_this) {
return function(x) {
return x * x;
};
})(this);
使用 JavaScript 的 arguments 对象是一种处理接收不定数量个参数的函数常用办法. CoffeeScript 在函数定义和调用里提供了变参(splats) ...
的语法, 让不定个数的参数使用起来更愉悦一些.
gold = silver = rest = "unknown"
awardMedals = (first, second, others...) ->
gold = first
silver = second
rest = others
contenders = [
"Michael Phelps"
"Liu Xiang"
"Yao Ming"
"Usain Bolt"
]
awardMedals contenders...
alert "The Field: " + rest
解释为:
var awardMedals, contenders, gold, rest, silver,
__slice = [].slice;
gold = silver = rest = "unknown";
awardMedals = function() {
var first, others, second;
first = arguments[0], second = arguments[1], others = 3 <= arguments.length ? __slice.call(arguments, 2) : [];
gold = first;
silver = second;
return rest = others;
};
contenders = ["Michael Phelps", "Liu Xiang", "Yao Ming", "Usain Bolt"];
awardMedals.apply(null, contenders);
alert("The Field: " + rest);
输出为:[“Yao Ming”, “Usain Bolt”]
注意:参数调用的时候需要在后面加上...
,否则会只传入一个参数
CoffeeScript 编译器会考虑所有变量, 保证每个变量都在词法域里适当地被定义 — 你永远不需要自己去写 var
.
outer = 1
changeNumbers = ->
inner = -1
outer = 10
inner = changeNumbers()
解释为:
var changeNumbers, inner, outer;
outer = 1;
changeNumbers = function() {
var inner;
inner = -1;
return outer = 10;
};
inner = changeNumbers();
所有变量的定义都被推到相关的顶层作用域, 也就是第一次出现的位置. outer 在内层的函数里没有被重新定义, 因为它已经存在于作用域当中了. 同时, 内层函数里的 inner 不应该改变外部的同名的变量, 所以在这里有自己的声明.
这部分比较好理解,直接看代码就好。不同的一点是coffee中有了unless关键字,等同于 if not
mood = greatlyImproved if singing
unless happy and knowsIt
clapsHands()
chaChaCha()
else
showIt()
date = if friday then sue else jill
相当于
var date, mood;
if (singing) {
mood = greatlyImproved;
}
if (!(happy && knowsIt)) {
clapsHands();
chaChaCha();
} else {
showIt();
}
date = friday ? sue : jill;
在 while, if/else, switch/when 的语句当中, then 可以被用来分隔判断条件跟表达式, 这样就不用强制写换行或者分号了.
在很多时候,换行可以代替,
song = ["do", "re", "mi", "fa", "so"]
singers = Jagger: "Rock", Elvis: "Roll"
bitlist = [
1, 0, 1
0, 0, 1
1, 1, 0
]
kids =
brother:
name: "Max"
age: 11
sister:
name: "Ida"
age: 9
数组也可以通过范围来表示,例如
a = [1..10]
a = [1...10]
分别编译为
a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
a = [1, 2, 3, 4, 5, 6, 7, 8, 9];
也就是,..
是包括最后一个数据,而...
不包括最后一个数据。
作用类似slice,和range生成数组有点相似,但作为下标范围时两端都有缺省值,左边缺省时默认为0,右边缺省时默认为最大下标。返回值是切割后的数组。
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9]
start = numbers[0..2]
middle = numbers[3...-2]
end = numbers[-2..]
copy = numbers[..]
numbers[2..5] = [-3, -4, -5, -6]
编译为:
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9];
start = numbers.slice(0, 3); // [1,2,3]
middle = numbers.slice(3, -2); // [4,5,6,7]
end = numbers.slice(-2); // [8,9]
copy = numbers.slice(0); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
[].splice.apply(numbers, [2, 4].concat(_ref = [-3, -4, -5, -6])), _ref; // [1, 2, -3, -4, -5, -6, 7, 8, 9]
eat food for food in ['toast', 'cheese', 'wine']
courses = ['greens', 'caviar', 'truffles', 'roast', 'cake']
menu i + 1, dish for dish, i in courses
foods = ['broccoli', 'spinach', 'chocolate']
eat food for food in foods when food isnt 'chocolate'
编译为:
var courses, dish, food, foods, i, _i, _j, _k, _len, _len1, _len2, _ref;
_ref = ['toast', 'cheese', 'wine'];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
food = _ref[_i];
eat(food);
}
courses = ['greens', 'caviar', 'truffles', 'roast', 'cake'];
for (i = _j = 0, _len1 = courses.length; _j < _len1; i = ++_j) {
dish = courses[i];
menu(i + 1, dish);
}
foods = ['broccoli', 'spinach', 'chocolate'];
for (_k = 0, _len2 = foods.length; _k < _len2; _k++) {
food = foods[_k];
if (food !== 'chocolate') {
eat(food);
}
}
nums = []
for num, i in nums
其中,i是下标。效果等同于:
nums = []
for (i = _i = 0, _len = nums.length; _i < _len; i = ++_i) {
num = nums[i];
}
数组遍历还可以加by
,达到固定跨度迭代的目的。例如:
nums = []
for num, i in nums by 2
yearsOld = max: 10, ida: 9, tim: 11
ages = for child, age of yearsOld
"#{child} is #{age}"
编译为:
var age, ages, child, yearsOld;
yearsOld = {
max: 10,
ida: 9,
tim: 11
};
ages = (function() {
var _results;
_results = [];
for (child in yearsOld) {
age = yearsOld[child];
_results.push("" + child + " is " + age);
}
return _results;
})();
如果你希望仅迭代在当前对象中定义的属性,通过hasOwnProperty检查并 避免属性是继承来的,可以这样来写:
for own key, value of object
例如,上例可以改为
ages = for child, age of yearsOld
编译出来的结果就变成:
var age, ages, child, yearsOld,
__hasProp = {}.hasOwnProperty;
yearsOld = {
max: 10,
ida: 9,
tim: 11
};
ages = (function() {
var _results;
_results = [];
for (child in yearsOld) {
if (!__hasProp.call(yearsOld, child)) continue;
age = yearsOld[child];
_results.push("" + child + " is " + age);
}
return _results;
})();
CoffeeScript仅提供了一种底层循环,即while循环。与JavaScript中的while 循环的主要区别是,在CoffeeScript中while可以作为表达式来使用, 而且可以返回一个数组,该数组包含每个迭代项的迭代结果。
CoffeeScript还提供了另一个关键字,until,效果等同于while not
buy() while supply > demand
sell() until supply > demand
等同于
while (supply > demand) {
buy();
}
while (!(supply > demand)) {
sell();
}
使用 JavaScript 循环生成函数的时候, 经常会添加一个闭包来包裹代码, 这样做目的是为了循环的变量被保存起来, 而不是所有生成的函数搜去访问最后一个循环的变量. CoffeeScript 提供了一个 do 关键字, 用来直接调用跟在后边的函数, 并且传递需要的参数.
for filename in list
do (filename) ->
fs.readFile filename, (err, contents) ->
compile filename, contents.toString()
编译为:
var filename, _fn, _i, _len;
_fn = function(filename) {
return fs.readFile(filename, function(err, contents) {
return compile(filename, contents.toString());
});
};
for (_i = 0, _len = list.length; _i < _len; _i++) {
filename = list[_i];
_fn(filename);
}
有些代码在 JavaScript 当中要写不少的语句, 而在 CoffeeScript 中只是表达式的一部分, 这些代码的编译结果会自动生成一个闭包. 这个写法很有用, 比如把列表解析的结果赋值给变量:
globals = (name for name of window)[0...10]
编译为:
var globals, name;
globals = ((function() {
var _results;
_results = [];
for (name in window) {
_results.push(name);
}
return _results;
})()).slice(0, 10);
由于操作符 ==
常常带来不准确的约束, 不容易达到效果, 而且跟其他语言当中意思不一致, CoffeeScript 会把 ==
编译为 ===
, 把 !=
变异为 !==
. 此外, is
编译我 ===
, 而 isnt
编译为!==
.
not
可以作为 !
的 alias 使用.
逻辑操作方面, and
编译为&&
, 而 or
编译为||
.
on
和 yes
跟 true
是一样的, 而 off
和 no
是布尔值 false
.
unless
可以认为是 if
相反的版本.
this.property
简短的写法可以用 @property
.
可以用 in
判断数据在数组中是否出现, 而 of
可以探测 JavaScript
对象的属性是否存在.
为了简化数学表达式, **
可以用来表示乘方, //
表示整除, %%
提供数学的模运算(译注: true mathematical modulo?).
完整的列表:
在 JavaScript 里检测一个变量的存在性有点麻烦. if (variable) ...
比较接近答案, 但是对 0
不成立. CoffeeScript 的存在性操作符?
除非是null
或者 undefined
, 否则都返回 true
例如:
solipsism = true if mind? and not world?
speed = 0
speed ?= 15
footprints = yeti ? "bear"
编译为:
var footprints, solipsism, speed;
if ((typeof mind !== "undefined" && mind !== null) && (typeof world === "undefined" || world === null)) {
solipsism = true;
}
speed = 0;
if (speed == null) {
speed = 15;
}
footprints = typeof yeti !== "undefined" && yeti !== null ? yeti : "bear";
存在性操作符 ?.
的访问器的变体可以用来吸收链式属性调用中的 null. 数据可能是 null 或者 undefined 的情况下可以用这种写法替代访问器.
。 如果所有属性都存在, 那么你会得到想要的结果, 如果链式调用有问题, 会返回 undefined 而不是抛出 TypeError.
zip = lottery.drawWinner?().address?.zipcode
编译为:
var zip, _ref;
zip = typeof lottery.drawWinner === "function" ? (_ref = lottery.drawWinner().address) != null ? _ref.zipcode : void 0 : void 0;
CoffeeScript 提供了一个基础的class
结构, 你可以在一个定义的表达式里完成命名 class, 定义父类, 赋值原型上的属性, 定义构造器。同时用@attr
表示this.attr
class Animal
constructor: (@name) ->
move: (meters) ->
alert @name + " moved #{meters}m."
class Snake extends Animal
move: ->
alert "Slithering..."
super 5
class Horse extends Animal
move: ->
alert "Galloping..."
super 45
sam = new Snake "Sammy the Python"
tom = new Horse "Tommy the Palomino"
sam.move()
tom.move()
相当于:
var Animal, Horse, Snake, sam, tom,
__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() {
function Animal(name) {
this.name = name;
}
Animal.prototype.move = function(meters) {
return alert(this.name + (" moved " + meters + "m."));
};
return Animal;
})();
Snake = (function(_super) {
__extends(Snake, _super);
function Snake() {
return Snake.__super__.constructor.apply(this, arguments);
}
Snake.prototype.move = function() {
alert("Slithering...");
return Snake.__super__.move.call(this, 5);
};
return Snake;
})(Animal);
Horse = (function(_super) {
__extends(Horse, _super);
function Horse() {
return Horse.__super__.constructor.apply(this, arguments);
}
Horse.prototype.move = function() {
alert("Galloping...");
return Horse.__super__.move.call(this, 45);
};
return Horse;
})(Animal);
sam = new Snake("Sammy the Python");
tom = new Horse("Tommy the Palomino");
sam.move();
tom.move();
如果你不喜欢用 class 的裁判法定义原型, CoffeeScript 提供了一些低级的方便写法. extends 操作符可以用来恰当地定义任何一对构造函数的原型链; 用 :: 可以快速访问对象的原型; super() 可以编译为一个父类上同名方法的调用.
String::dasherize = ->
this.replace /_/g, "-"
等价于:
String.prototype.dasherize = function() {
return this.replace(/_/g, "-");
};
还有一些零碎的特性,例如字符串替换、块级正则表达式、块级字符串等特性,在这里不特殊说明了,建议查看官方文档